Skip to main content
Search
Search the documentation

Stepper

Multi-step flows with validation that waits, steps that cannot be skipped, and focus that follows.

pnpm dlx shadcn-svelte@latest add https://coral.imlargo.dev/r/kit-stepper.json

A wizard is a currentStep number until it has to validate. Then Next needs to wait on a request and stay put when it fails, the step list has to refuse a jump past a step nobody filled in, and after Next the focus is on a button that may no longer exist. For a keyboard user, that's the page body.

Content for the "Repository" step.

What Coral adds

  • Validation that waits: onbeforenext runs on Next with the step being left. Return a promise and the stepper waits; return false or throw and it stays. The Next button reports it is busy and keeps focus.
  • Linear by completion: a step can be jumped to once every step before it is complete. After going back to fix step 1, step 4 is still one click away.
  • Focus follows. After Next or Previous, focus moves to the new panel, which is named by its step, so a screen reader says where the reader landed. Only when focus was inside the stepper.
  • aria-current="step" on the current step, and an ordered list, so position is announced for free.
  • Arrow keys between steps, on top of Tab. Right-to-left aware.

Validation

Try «My Service».

Next on the last step runs onbeforenext, then onfinish, under the same false convention.

Steps are whatever you pass (strings, ids, the step objects themselves), compared with ===. Prefer ids when binding value: an object assigned into a $state comes back wrapped in a proxy, which is no longer === to the object in steps. Bind to $state.raw if the steps must be objects.

Composition

Stepper
├── StepperList
│   └── StepperItem ×n       → button, children({ state, index, disabled })
├── StepperContent ×n        → shown for its step
├── StepperPrevious
└── StepperNext              → children({ isLast, pending })

Stepper's own children snippet receives the shared state (value, next(), goTo(), stateOf()) for a layout the pieces do not cover.

One form across steps

StepperContent unmounts the panels that are not showing, and an unmounted field is not submitted. When every step's fields post in a single <form>, set keepMounted: inactive panels stay in the page with hidden. Validate each step in onbeforenext: the browser cannot focus a required field inside a hidden panel to report it.

Import

<script lang="ts">
	import Stepper from '#lib/components/coral/kit/stepper/stepper.svelte';
	import StepperItem from '#lib/components/coral/kit/stepper/stepper-item.svelte';
	// ...one import per piece you use
</script>

Props

Stepper

PropTypeDefaultDescription
stepsT[]-Every step, in order. Required.
valueTfirst stepBindable.
completedT[][]Bindable. Steps finished with Next.
linearbooleantrueRefuses jumping past an incomplete step.
onbeforenext(step: T) => unknown-Async-aware. false or throw stays.
onfinish() => unknown-Next on the last step.
onerror(error: unknown) => void-What onbeforenext or onfinish threw.
onvaluechange(step: T) => void-The current step changed.
orientation'horizontal' | 'vertical'horizontalArrow keys and layout direction.
childrenSnippet<[StepperContext<T>]>-The pieces, with the shared state.

StepperItem

PropTypeDescription
stepTWhich step. Required.
itemClassstringMerged onto the <li>.
childrenSnippet<[{ state, index, disabled }]>What the step shows. Required.

state is complete, current or upcoming, also exposed as data-state for styling.

StepperContent

PropTypeDefaultDescription
stepT-Which step. Required.
keepMountedbooleanfalseKeeps inactive panels in the page, hidden.

StepperPrevious

PropTypeDefaultDescription
labelstringPreviousThe label, when the children are left out.
childrenSnippet-Replaces the label.

StepperNext

PropTypeDefaultDescription
nextLabelstringNextThe label on every step but the last.
finishLabelstringFinishThe label on the last step.
childrenSnippet<[{ isLast, pending }]>-Replaces the label. Receives where the stepper is.

Both accept the shadcn button's props as well.