Stepper
v0.0.10
mix mishka.ui.gen.headless stepper
Overview of the Phoenix and LiveView Headless Stepper Component
The Mishka Chelekom Headless Stepper shows progress through an ordered flow in Phoenix and LiveView. It renders an <ol> with one <li> for each step slot, because the order is the meaning. You give the root an active index and every state follows from it: earlier steps are complete, the step at active is current and the rest are incomplete. The current step carries aria-current="step".
The component ships no colors or sizes. The root takes class, and the parts take step_class, indicator_class, content_class, label_class, description_class and action_class. The slot body goes inside the indicator, so put an icon or a number there. Style the states with data-[state=complete] and data-[state=current] on the step.
Base UI Stepper example - four steps, the third current (Phoenix headless)
active={2} makes Cart and Address complete, Payment current and Done incomplete. The first two steps hold a check icon, the others hold their number.
Base UI Stepper example - four steps, the third current (Phoenix headless)
- Cart
- Address
- Payment
- Done
<.stepper
id="baseui-stepper-hero"
label="Checkout"
active={2}
class="inline-grid auto-cols-fr grid-flow-col gap-2 text-sm text-neutral-950 dark:text-white"
step_class="flex items-center gap-2 [&_[data-part=indicator]]:grid [&_[data-part=indicator]]:size-7 [&_[data-part=indicator]]:place-items-center [&_[data-part=indicator]]:rounded-full [&_[data-part=indicator]]:border [&_[data-part=indicator]]:border-neutral-300 dark:[&_[data-part=indicator]]:border-neutral-700 [&_[data-part=label]]:text-sm [&_[data-part=description]]:text-xs [&_[data-part=description]]:text-neutral-500 data-[state=complete]:[&_[data-part=indicator]]:border-neutral-950 data-[state=complete]:[&_[data-part=indicator]]:bg-neutral-950 data-[state=complete]:[&_[data-part=indicator]]:text-white data-[state=current]:[&_[data-part=indicator]]:border-neutral-950 dark:data-[state=complete]:[&_[data-part=indicator]]:border-white dark:data-[state=complete]:[&_[data-part=indicator]]:bg-white dark:data-[state=complete]:[&_[data-part=indicator]]:text-neutral-950 dark:data-[state=current]:[&_[data-part=indicator]]:border-white"
>
<:step label="Cart"><.icon name="hero-check" class="size-3.5" /></:step>
<:step label="Address"><.icon name="hero-check" class="size-3.5" /></:step>
<:step label="Payment">3</:step>
<:step label="Done">4</:step>
</.stepper>Base UI Stepper example - vertical with descriptions (Phoenix headless)
orientation="vertical" stacks the steps. Each step shows a description under its label.
Base UI Stepper example - vertical with descriptions (Phoenix headless)
- Cart Two items
- Address Where it goes
- Payment How you pay
<.stepper
id="baseui-stepper-vertical"
label="Checkout"
active={1}
orientation="vertical"
class="flex flex-col gap-4 text-sm text-neutral-950 dark:text-white"
step_class="flex items-center gap-2 [&_[data-part=indicator]]:grid [&_[data-part=indicator]]:size-7 [&_[data-part=indicator]]:place-items-center [&_[data-part=indicator]]:rounded-full [&_[data-part=indicator]]:border [&_[data-part=indicator]]:border-neutral-300 dark:[&_[data-part=indicator]]:border-neutral-700 [&_[data-part=label]]:text-sm [&_[data-part=description]]:text-xs [&_[data-part=description]]:text-neutral-500 data-[state=complete]:[&_[data-part=indicator]]:border-neutral-950 data-[state=complete]:[&_[data-part=indicator]]:bg-neutral-950 data-[state=complete]:[&_[data-part=indicator]]:text-white data-[state=current]:[&_[data-part=indicator]]:border-neutral-950 dark:data-[state=complete]:[&_[data-part=indicator]]:border-white dark:data-[state=complete]:[&_[data-part=indicator]]:bg-white dark:data-[state=complete]:[&_[data-part=indicator]]:text-neutral-950 dark:data-[state=current]:[&_[data-part=indicator]]:border-white"
>
<:step label="Cart" description="Two items"><.icon name="hero-check" class="size-3.5" /></:step>
<:step label="Address" description="Where it goes">2</:step>
<:step label="Payment" description="How you pay">3</:step>
</.stepper>Step slot attributes
A step slot takes label, description, content, state, navigate, patch, href, disabled and class. content is written to data-content on the indicator, for a skin that draws the indicator text with CSS. state overrides the derived state with complete, current or incomplete, which suits a flow that skips a step or fails one. The slot class is added to the step.
<.stepper id="signup" label="Sign up" active={2}>
<:step label="Account" state="complete">1</:step>
<:step label="Profile" state="incomplete">2</:step>
<:step label="Confirm">3</:step>
</.stepper>Selectable steps
Steps are inert markup until you give them somewhere to go. on_select takes a LiveView event name or a Phoenix.LiveView.JS command and turns each step you can reach into a <button type="button"> that pushes %{"index" => index}. A step is reachable up to and including the current one. Add allow_next to let visitors select later steps too. A step with navigate, patch or href becomes a link. A step with disabled, or one that is not reachable, is plain text and takes no tab stop.
The action is an empty element inside the step. Make it cover the step with step_class="relative" and action_class="absolute inset-0" so the whole step is the click target. Give the stepper an id so the action takes its name from the step label through aria-labelledby. Without an id the label text is rendered inside the action instead.
<.stepper
id="checkout"
label="Checkout"
active={@step}
on_select="go_to_step"
step_class="relative flex items-center gap-2"
action_class="absolute inset-0 cursor-pointer"
>
<:step label="Cart">1</:step>
<:step label="Address">2</:step>
<:step label="Payment">3</:step>
</.stepper>def handle_event("go_to_step", %{"index" => index}, socket) do
{:noreply, assign(socket, :step, index)}
endResponsive orientation
horizontal_from takes sm, md, lg or xl and is written to data-orientation-from on the root. It is a hook for your own styles, for a stepper that is vertical on a narrow screen and horizontal from that breakpoint up. The component does not apply the breakpoint itself.
Parts & data attributes
Each part carries a stable data-part and a structural chelekom-stepper__* class you can target, plus the state attributes below.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Root | root | aria-label, data-orientation, data-orientation-from | The ordered list. |
| Step | step | data-index, data-state, data-disabled, aria-current | One list item. data-state is complete, current or incomplete, and aria-current="step" is set on the current one. |
| Indicator | indicator | aria-hidden, data-content | Holds the slot body, such as an icon or a number. |
| Content | content | - | Wraps the label and the description. |
| Label | label | id | The step's name. When the stepper has an id, this span gets the id of the stepper plus -label- and the step index. |
| Description | description | - | Secondary text under the label. |
| Action | action | aria-labelledby | A button or a link. Rendered only for a step the visitor can select. |
Accessibility
There is no formal WAI-ARIA APG pattern for a stepper. The steps are an ordered list, the current step is announced through aria-current="step", and a selectable step is a native button or link that the keyboard reaches with Tab and activates with Enter, or Space for a button. A step that cannot be selected is text, so it never takes a tab stop it cannot use.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | No | Unique id |
label
|
string
| — | — | No | Accessible name for the list |
active
|
integer
|
0
| — | No | Zero-based index of the current step; earlier steps are complete |
orientation
|
string
|
horizontal
|
horizontalvertical
| No | Layout direction, reflected as data-orientation |
horizontal_from
|
string
| — |
smmdlgxl
| No | Breakpoint at which a vertical stepper turns horizontal (data-orientation-from) |
on_select
|
any
| — | — | No | A JS command or LiveView event name; makes every selectable step a button |
allow_next
|
boolean
|
false
| — | No | Also let steps after the current one be selected |
class
|
any
| — | — | No | Extra classes for the root |
step_class
|
any
| — | — | No | — |
indicator_class
|
any
| — | — | No | — |
action_class
|
any
| — | — | No | — |
content_class
|
any
| — | — | No | — |
description_class
|
any
| — | — | No | — |
label_class
|
any
| — | — | No | — |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
step
| No | One step |
Attributes of the step slot
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
label
|
string
| — | — | No | The step's visible name |
description
|
string
| — | — | No | Secondary line under the label |
content
|
string
| — | — | No | Overrides the number the skin would draw in the indicator |
state
|
string
| — | — | No | Force complete | current | incomplete |
navigate
|
string
| — | — | No | — |
patch
|
string
| — | — | No | — |
href
|
string
| — | — | No | — |
disabled
|
boolean
| — | — | No | — |
class
|
any
| — | — | No | — |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
stepper
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless stepper