Kanzo UI
Navigation

Steps

A stepper for a multi-stage flow, with per-step content and navigation.

Ark UI
A party has signed, and has not left yet.

Usage

import {
  Steps,
  StepsCompletedContent,
  StepsContent,
  StepsDescription,
  StepsIndicator,
  StepsItem,
  StepsList,
  StepsNext,
  StepsPrevious,
  StepsSeparator,
  StepsTitle,
  StepsTrigger,
} from "@kanzo-tech/ui";
<Steps count={3} defaultStep={0}>
  <StepsList>
    <StepsItem index={0}>
      <StepsTrigger>
        <StepsIndicator>1</StepsIndicator>
        <StepsTitle>Connect</StepsTitle>
      </StepsTrigger>
      <StepsSeparator />
    </StepsItem>
  </StepsList>
  <StepsContent index={0}>…</StepsContent>
</Steps>

count is required — the machine needs to know the total to decide when the flow is complete. StepsItem and StepsContent are matched by index.

Anatomy

<Steps count>
  <StepsList>
    <StepsItem index>
      <StepsTrigger>
        <StepsIndicator />   ← number, swapped for a check once complete
        <StepsTitle />
        <StepsDescription />
      <StepsSeparator />     ← hidden on the last item
  <StepsContent index />
  <StepsCompletedContent />  ← shown after the final step
  <StepsPrevious /> <StepsNext />

StepsIndicator renders its children and a CheckIcon, showing whichever the step's state calls for — you always pass the number, never the tick.

Vertical

StepsSeparator absolutely positions itself down the indicator column in vertical mode, so the connecting line follows the list without any extra markup.

Only members the board has not already sent out.

Accessibility

The triggers announce themselves as tabs and no arrow key moves between them. Ark's machine emits role="tablist" and role="tab" with the aria-selected / aria-controls wiring, and @zag-js/steps@1.41.2 ships no key handler at all — Home End all do nothing. @zag-js/tabs, the same version by the same team, implements all six.

So a screen-reader user is told this is a tab list, tries the keys that a tab list answers, and gets nothing. What works today is Tab and Shift+Tab: with the default linear={false} every trigger is its own tab stop, so the list is reachable and operable — just not the way the role advertises.

KeyWhat happens
Tab / Shift+TabMoves to the next / previous trigger. Every trigger is a stop.
Enter / SpaceActivates the focused trigger, as a button.
Home EndNothing.

linear={true} is not the fix and is a worse state: it drops the non-current triggers to tabIndex: -1 and makes their click handler return early, so a step becomes unreachable by pointer and keyboard alike while still announcing role="tab". The reasoning is in the references, and packages/ui/src/simples/steps.test.ts holds the measurement — it fails on the day Ark ships the keymap, which is when this section should be rewritten.

If a keyboard-complete stepper is a requirement today, drive Tabs with step-shaped styling instead; it is the same role with the keymap behind it.

API Reference

All props are Ark's Steps.Root; the components add styling and the indicator's complete-state swap only.

PropTypeDefault
countnumber
step / defaultStepnumber0
onStepChange(details: { step: number }) => void
orientation"horizontal" | "vertical""horizontal"
linearbooleanfalse

useSteps re-exports Ark's useStepsContext for reading the current step from a descendant.

On this page