Accessibility
Stepper, StepperState from @prepared911/ui/stepper.
ol with role="list" (Box adds the role, because WebKit exposes a list without markers as a group) and one li per step. A screen reader announces the list name and the number of steps on entering it.aria-label or by aria-labelledby, which the types require. In the live guidance panel the header names it.aria-current="step". No other step carries aria-current.stateLabels: "Completed, Confirm nature of event". The status marks (check, spinner, ring, question mark) are decorative and aria-hidden. Color and shape carry no state alone.announcement is written to the polite live region of the list's document. It is one sentence per update: a step that became current wins over one that became complete. Nothing is said on mount, for a step new to the list, or when no state changed. Without announcement nothing is said, and the state is still in the list.
The steps are not controls, so they take no tab stop. In the live guidance panel the header is one button (Enter and Space collapse and expand it) and the scroll region is focusable, so the arrow keys scroll a long list.
forced-colors: active the accent fill is dropped by the browser, so the current row draws a Highlight outline inside its edge; the success disc is CanvasText with a Canvas check, and the ring edge is CanvasText.data-motion="reduced"). The row stays filled and keeps aria-current, so the state does not rely on movement. The fill and text colors fade over a short glide.rem-based, so a row is 72px at 200% text size and nothing scrolls sideways at 320px.aria-current, and the single polite sentence when a step moves.