Stepper
Multi-step progress - onboarding, checkout, wizards. Steps are plain maps; three arrangements.
Steps from plain maps
Multi-step progress for onboarding, checkout, wizards. Steps are plain maps - name, description, complete?, active?, and an optional on_click JS command to make them jump targets. aria-current and completed labels are wired for screen readers. Beside-labels run wide, so a constrained container gives the rail an overflow-x-auto wrapper and lets it scroll.
<div class="overflow-x-auto">
<.stepper steps={[
%{name: "Account", description: "Email and password", complete?: true, active?: false},
%{name: "Workspace", description: "Name your project", complete?: false, active?: true},
%{name: "Invite", description: "Bring the team", complete?: false, active?: false},
%{name: "Review", description: "Confirm and finish", complete?: false, active?: false}
]} />
</div>
Circles that don't need names
Leave name and description off a step and the label block isn't rendered at all, so you get circles and connectors with nothing padding them out. No variant to reach for: it's the same stepper with less in the maps. Pairs with size="xs" when the rail is a status line above a form rather than the page's headline.
<.stepper
size="xs"
steps={[
%{complete?: true, active?: false},
%{complete?: true, active?: false},
%{complete?: false, active?: true},
%{complete?: false, active?: false},
%{complete?: false, active?: false}
]}
/>
Labels underneath
label_placement="bottom" is the classic wizard look - circles in a row, labels centred underneath, connectors pinned to the circle centres. Horizontal only, at every width - on small screens the rail compresses rather than stacking.
<.stepper
label_placement="bottom"
steps={[
%{name: "Cart", complete?: true, active?: false},
%{name: "Shipping", complete?: true, active?: false},
%{name: "Payment", complete?: false, active?: true},
%{name: "Confirm", complete?: false, active?: false}
]}
/>
Vertical
orientation="vertical" runs the rail down the side - the settings-checklist and deploy-pipeline arrangement, with descriptions beside each circle.
<.stepper
orientation="vertical"
steps={[
%{
name: "Repository connected",
description: "GitHub app installed",
complete?: true,
active?: false
},
%{name: "First deploy", description: "Build and release", complete?: false, active?: true},
%{
name: "Custom domain",
description: "DNS and certificates",
complete?: false,
active?: false
}
]}
/>
Segments instead of circles
variant="bars" swaps the numbered discs for a row of 4px segments and drops the connectors, because the gaps between segments are already the rail. Done and current fill solid, ahead of you stays gray. The numerals stay in the DOM for screen readers, and clicking a segment still fires that step's on_click. Horizontal only.
<.stepper
variant="bars"
steps={[
%{complete?: true, active?: false},
%{complete?: true, active?: false},
%{complete?: false, active?: true},
%{complete?: false, active?: false},
%{complete?: false, active?: false}
]}
/>
Segments with titles
Give the steps names and each title sits under its own segment, left-aligned to it, with the ones you haven't reached reading a notch back. Below sm every title but the current step's hides, so the segments carry the count on a phone and the aria-labels keep the names.
<.stepper
variant="bars"
steps={[
%{name: "Cart", complete?: true, active?: false},
%{name: "Address", complete?: true, active?: false},
%{name: "Payment", complete?: false, active?: true},
%{name: "Confirm", complete?: false, active?: false}
]}
/>
One label for the whole rail
The other way to label a stepper: nameless segments, then a single line naming the step you're on with a count beside it. That's composition, not an attr - the stepper renders the rail and you own the row underneath, which is also where Back and Next belong. Copy it as the shape for a wizard footer.
Payment
Step 3 of 4
<div class="w-full max-w-md">
<.stepper
variant="bars"
steps={[
%{complete?: true, active?: false},
%{complete?: true, active?: false},
%{complete?: false, active?: true},
%{complete?: false, active?: false}
]}
/>
<div class="flex items-baseline justify-between mt-4">
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">Payment</p>
<p class="text-sm text-gray-500 tabular-nums dark:text-gray-400">Step 3 of 4</p>
</div>
<div class="flex items-center justify-between mt-4">
<.button color="gray" variant="outline" size="sm" label="Back" />
<.button size="sm" label="Continue" />
</div>
</div>
Properties
| Attribute | Type | Default | Description |
|---|---|---|---|
class
|
string |
""
|
|
label_placement
|
string |
"beside"
|
horizontal only: bottom centres the labels under the circles (the classic wizard look); ignored when vertical, where labels always sit beside
one of: "beside", "bottom"
|
orientation
|
string |
"horizontal"
|
one of: "horizontal", "vertical"
|
size
|
string |
"md"
|
one of: "xs", "sm", "md", "lg"
|
steps*
|
list |
||
variant
|
string |
"circles"
|
bars trades the numbered circles for a row of segments - the progress-bar read, with each step's title under its own segment. Horizontal only; ignored when vertical, and it takes precedence over label_placement (a bar's title always sits under it)
one of: "circles", "bars"
|