Stepper component
components specs/components/stepper.kmd
A standalone step indicator: an ordered sequence of state circles joined by connector lines that shows a workflow's progress. Each step is completed (check), current (filled dot), upcoming (hollow outline), or error (bang). A linear stepper is a passive read-out; a non-linear one lets the user click a completed step to jump back. Horizontal or vertical, with an optional label per step. gov.br ships a standalone `step`; KDS had a wizard-multistep pattern but no reusable stepper component — this is it (reused by the wizard).
Quando esta spec se aplica
Triggers primários
- Render a stepper / step indicator
Todos os triggers
- Show progress through an ordered multi-step workflow (checkout, form wizard)
- Add a standalone step indicator (gov.br `step`, not the full wizard)
- Let a user jump back to a completed step
Corpo da especificação
Stepper
R1 — Anatomy
A stepper is an ordered sequence of steps. Each step renders a state
circle (Ø 28px) and an optional label (a caller-composed child — the
component is font-free, so a label is a Text/child widget, never a glyph the
stepper draws). Adjacent circles are joined by a connector line (length 32px,
stroke 2px). The label sits below (horizontal) or beside (vertical) its circle,
separated by one --kds-space-2 (8px) gap.
R2 — States
Each step is in exactly one state, painted from the active Palette:
| State | Circle | Mark | Token |
|---|---|---|---|
completed | filled | check (✓) | accent fill · accent-on mark |
current | filled | centered dot (0.16 × Ø) | accent fill · accent-on dot |
upcoming | hollow outline | — | border/outline stroke |
error | filled | bang (!) | error fill · accent-on mark |
The colours resolve through the DS's Palette (design-RFC-014), so a stepper
re-themes and re-colours per active theme and design system with no per-widget
override.
R3 — Orientation
horizontal (default) lays steps left→right, connectors horizontal; vertical
lays them top→bottom, connectors vertical. Orientation drives the keyboard axis
(R6).
R4 — Linear vs non-linear
- linear (default): a passive progress read-out — no step is activatable.
- non-linear: a completed step is clickable to jump back to it,
firing
on_step(index). Only completed steps are reachable; current, upcoming, and error steps are never activatable (a user cannot skip ahead past incomplete work). The current step is never a jump target of itself.
R5 — Accessibility semantics
The stepper exports a SemRole::Group carrying its accessible name, holding one
node per step:
- a reachable step (completed, in a non-linear stepper) is a
SemRole::Button; - every other step is a
SemRole::Label.
Each step node carries its state as its accessible value
(completed/current/upcoming/error) so assistive tech announces
aria-current on the current step. The roving-focus step (R6) is marked
focused.
R6 — Keyboard (roving focus, one Tab stop)
Per navigation.kmd R10 / progress-indicator roving-focus shape:
- Tab enters the stepper only if it has a reachable step; it is a single Tab stop (roving focus, not one stop per step).
- The orientation's arrows (Left/Right horizontal, Up/Down vertical) move a roving cursor across the reachable steps and wrap.
- Home/End jump the cursor to the first/last reachable step.
- Enter/Space activates the cursor's step (fires
on_step).
A fully-linear stepper (no reachable step) takes no Tab stop — it is a passive indicator, correctly skipped by keyboard navigation.
R7 — gov.br parity
Satisfies the gov.br step standalone component (govbr-ds-parity, GVB-25 /
stack#400): the same completed/current/upcoming/error progression and
horizontal/vertical layout. The KDS wizard pattern composes this stepper rather
than re-implementing the indicator.
Não-escopo
- The multi-step wizard flow (panel swapping, validation-gated advance) — that pattern composes the stepper; it is not this component.
- Label typography — a label is a caller-composed child; its type comes from
specs/fonts/typography.kmd, not this spec. - Numbered-vs-dot circle content beyond the R2 state marks — the state mark is the canonical indicator; a numbered variant, if added, is a follow-up that must keep the R2 states and R5 semantics.
Requirements (testable)
Requirement: State rendering {#req-stepper-state-rendering}
Each step SHALL render a 28px state circle in exactly one of completed, current, upcoming, or error, with the circle fill and mark resolved from the active Palette per the R2 state table.
Scenario: Completed step paints check on accent fill
- GIVEN a stepper with a step whose state is
completed - WHEN the stepper renders under the active
Palette - THEN that step's circle is filled with the
accenttoken - AND the step's mark is a check (✓) painted with the
accent-ontoken
Scenario: Upcoming step is a hollow outline with no mark
- GIVEN a stepper with a step whose state is
upcoming - WHEN the stepper renders
- THEN that step's circle is drawn as a hollow outline using the
border/outline stroke - AND no mark glyph is painted inside the circle
Scenario: Error step paints a bang
- GIVEN a stepper with a step whose state is
error - WHEN the stepper renders under the active
Palette - THEN that step's circle is filled with the
errortoken - AND the step's mark is a bang (!) painted with the
accent-ontoken
Requirement: Linear vs non-linear activation {#req-stepper-activation}
A linear stepper SHALL expose no activatable step; a non-linear stepper SHALL make only completed steps clickable to jump back, firing on_step(index), and SHALL NOT allow activating current, upcoming, or error steps.
Scenario: Clicking a completed step in a non-linear stepper jumps back
- GIVEN a non-linear stepper with a
completedstep at index 1 - WHEN the user clicks the step at index 1
- THEN
on_step(1)fires
Scenario: Upcoming step is never activatable
- GIVEN a non-linear stepper with an
upcomingstep at index 3 - WHEN the user clicks the step at index 3
- THEN no
on_stepcallback fires - AND the current step is unchanged
Scenario: Linear stepper activates nothing
- GIVEN a linear stepper containing a
completedstep - WHEN the user clicks that completed step
- THEN no
on_stepcallback fires
Requirement: Accessibility semantics {#req-stepper-a11y-semantics}
The stepper SHALL export a SemRole::Group carrying its accessible name, exposing each reachable (non-linear completed) step as a SemRole::Button, every other step as a SemRole::Label, and each step node's accessible value SHALL equal its state so assistive tech announces aria-current on the current step.
Scenario: Reachable completed step is a button, others are labels
- GIVEN a non-linear stepper with a
completedstep and anupcomingstep - WHEN the accessibility tree is queried
- THEN the root node has role
SemRole::Groupwith the stepper's accessible name - AND the completed step node has role
SemRole::Button - AND the upcoming step node has role
SemRole::Label
Scenario: Current step exposes its state as accessible value
- GIVEN a stepper whose step at index 2 is
current - WHEN the accessibility node for index 2 is inspected
- THEN its accessible
valueiscurrent
Requirement: Roving-focus keyboard navigation {#req-stepper-keyboard}
The stepper SHALL be a single Tab stop with roving focus that is entered by Tab only when it has a reachable step; its orientation arrows SHALL move the cursor across reachable steps and wrap, Home/End SHALL jump to the first/last reachable step, and Enter/Space SHALL activate the cursor's step firing on_step.
Scenario: Arrow wraps across reachable steps
- GIVEN a focused horizontal non-linear stepper whose cursor is on the last reachable step
- WHEN the user presses Right arrow
- THEN the roving cursor moves to the first reachable step
Scenario: Enter activates the cursor's step
- GIVEN a focused non-linear stepper with the roving cursor on a reachable completed step at index 1
- WHEN the user presses Enter
- THEN
on_step(1)fires
Scenario: Fully-linear stepper takes no Tab stop
- GIVEN a fully-linear stepper with no reachable step
- WHEN the user presses Tab to traverse the page
- THEN focus skips the stepper and lands on the next focusable element
Requirement: Orientation drives layout and keyboard axis {#req-stepper-orientation}
A horizontal stepper SHALL lay steps left→right with horizontal connectors and use Left/Right arrows, and a vertical stepper SHALL lay steps top→bottom with vertical connectors and use Up/Down arrows.
Scenario: Vertical stepper responds to Down arrow
- GIVEN a focused vertical non-linear stepper with the cursor on the first reachable step
- WHEN the user presses Down arrow
- THEN the roving cursor moves to the next reachable step downward
Scenario: Horizontal stepper ignores the cross-axis arrow
- GIVEN a focused horizontal stepper with the cursor on a reachable step
- WHEN the user presses Down arrow
- THEN the roving cursor position is unchanged
Referências
engines/sdk/kroma/src/widget/stepper.rsmeta/docs/stack/specs/navigation/back-behavior.kmdtools/design-gen/docs/govbr-ds-gap-analysis.md