Step Indicator
Communicates progress through a sequence of steps, showing which steps are completed, which one is active, and which are still ahead. Useful for multi-step forms, wizards, and onboarding flows.
Usage
Use a step indicator when a task is split into a clear, ordered sequence of steps. Steps are numbered starting from 1. The first item is step 1, the second is step 2, and so on. You keep the number of the currently active step in a variable in your Alpine scope and pass that variable to the component, so it stays in sync with "Next"/"Back" controls. Clicking a step also updates the variable, letting users jump straight to that step. The content of each marker is up to you: a number, an icon, or anything else.
For narrow spaces, set data-collapsed="true" to switch to a compact summary. Only the active step stays visible and the connectors hide, so the footprint stays roughly constant no matter how many steps there are. Add the optional counter and progress parts to show which step you are on. The collapsed layout keeps its constant footprint even with many steps, where a full row would otherwise overflow.
API Reference
Component attribute(s)
x-h-step-indicator
x-h-step-indicator-item
x-h-step-indicator-trigger
x-h-step-indicator-marker
x-h-step-indicator-content
x-h-step-indicator-title
x-h-step-indicator-description
x-h-step-indicator-separator
x-h-step-indicator-counter
x-h-step-indicator-progressAttributes
x-h-step-indicator
| Attribute | Type | Required | Description |
|---|---|---|---|
self | number | true | A reference to the variable that holds the number of the active step. Clicking a step writes the new number to it. |
| data-orientation | horizontalvertical | false | Layout direction. Defaults to horizontal. Reactive, so you can bind it to switch orientation at runtime. |
| data-collapsed | truefalse | false | When true, switches to the compact summary. Only the active step stays visible, connectors hide, and the counter and progress parts appear. Reactive, so you can bind it. Works in both orientations. |
x-h-step-indicator-item
| Attribute | Type | Required | Description |
|---|---|---|---|
self | number | true | The number of this step. The first step is 1, the next 2, and so on. |
Each item exposes its state through a data-state attribute of inactive, active, or completed, which the child parts use for their styling.
x-h-step-indicator-trigger
| Attribute | Type | Required | Description |
|---|---|---|---|
| data-non-interactive | boolean | false | When true, clicking this step does not change the active step. A common use is to let users return to steps they have already passed while preventing them from skipping ahead to steps they have not reached yet. |
x-h-step-indicator-counter
Place inside the step indicator to show a "Step X of Y" label. It is only visible when the indicator is collapsed. The total is counted from the items automatically, so it works with x-for.
| Attribute | Type | Required | Description |
|---|---|---|---|
| data-step-label | string | false | Word before the current step number. Defaults to Step. |
| data-of-label | string | false | Word between the current step and the total. Defaults to of. |
x-h-step-indicator-progress
Place inside the step indicator to show a thin progress bar that fills to the active step over the total. It is only visible when the indicator is collapsed and needs no attributes.
Examples
Default
<div x-data="{ step: 2, total: 3 }" class="vbox w-full gap-6">
<div x-h-step-indicator="step">
<div x-h-step-indicator-item="1">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>
<svg x-h-icon data-icon="circle-user" role="img" aria-label="step account"></svg>
</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Account</span>
<span x-h-step-indicator-description>Your details</span>
</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="2">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>
<svg x-h-lucide role="img" aria-label="step address" data-lucide="map-pin"></svg>
</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Address</span>
<span x-h-step-indicator-description>Shipping info</span>
</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="3">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>
<svg x-h-lucide role="img" data-lucide="banknote" aria-label="step payment"></svg>
</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Payment</span>
<span x-h-step-indicator-description>Confirm order</span>
</span>
</button>
</div>
</div>
<div class="hbox justify-between gap-2">
<button x-h-button data-variant="outline" @click="step = Math.max(step - 1, 1)" :disabled="step === 1">Back</button>
<button x-h-button data-variant="primary" @click="step = Math.min(step + 1, total)" :disabled="step === total">Next</button>
</div>
</div>Collapsed
Set data-collapsed="true" for a compact summary that keeps a constant footprint no matter how many steps there are. Only the active step stays visible, and the optional counter and progress parts show which step you are on. Bind data-collapsed to switch between the two layouts, as the toggle below does.
<div x-data="{ step: 3, total: 5, collapsed: true }" class="vbox w-full gap-6">
<div x-h-step-indicator="step" :data-collapsed="collapsed">
<div x-h-step-indicator-item="1">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>1</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Account</span>
<span x-h-step-indicator-description>Your details</span>
</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="2">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>2</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Address</span>
<span x-h-step-indicator-description>Shipping info</span>
</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="3">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>3</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Payment</span>
<span x-h-step-indicator-description>Confirm order</span>
</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="4">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>4</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Review</span>
<span x-h-step-indicator-description>Check details</span>
</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="5">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>5</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Done</span>
<span x-h-step-indicator-description>All set</span>
</span>
</button>
</div>
<div x-h-step-indicator-counter></div>
<div x-h-step-indicator-progress></div>
</div>
<div class="hbox justify-between gap-2">
<button x-h-button data-variant="outline" @click="step = Math.max(step - 1, 1)" :disabled="step === 1">Back</button>
<button x-h-button data-variant="transparent" @click="collapsed = !collapsed" x-text="collapsed ? 'Expand' : 'Collapse'"></button>
<button x-h-button data-variant="primary" @click="step = Math.min(step + 1, total)" :disabled="step === total">Next</button>
</div>
</div>Markers only
<div x-data="{ step: 2 }">
<div x-h-step-indicator="step">
<div x-h-step-indicator-item="1">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>1</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="2">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>2</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="3">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>3</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="4">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>4</span>
</button>
</div>
</div>
</div>Generated with x-for
Because each item's step is an expression, you can generate the steps from an array with x-for, deriving the step number from the loop index.
<div x-data="{ step: 2, steps: ['Account', 'Address', 'Payment'] }" class="vbox w-full gap-6">
<div x-h-step-indicator="step">
<template x-for="(label, i) in steps" :key="i">
<div x-h-step-indicator-item="i + 1">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker x-text="i + 1"></span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title x-text="label"></span>
</span>
</button>
<div x-h-step-indicator-separator x-show="i < steps.length - 1"></div>
</div>
</template>
</div>
<div class="hbox justify-between gap-2">
<button x-h-button data-variant="outline" @click="step = Math.max(step - 1, 1)" :disabled="step === 1">Back</button>
<button x-h-button data-variant="primary" @click="step = Math.min(step + 1, steps.length)" :disabled="step === steps.length">Next</button>
</div>
</div>Vertical
<div x-data="{ step: 2 }">
<div x-h-step-indicator="step" data-orientation="vertical">
<div x-h-step-indicator-item="1">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>1</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Account</span>
<span x-h-step-indicator-description>Your details</span>
</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="2">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>2</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Address</span>
<span x-h-step-indicator-description>Shipping info</span>
</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="3">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>3</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Payment</span>
<span x-h-step-indicator-description>Confirm order</span>
</span>
</button>
</div>
</div>
</div>Disabled trigger
<div x-data="{ step: 2 }">
<div x-h-step-indicator="step">
<div x-h-step-indicator-item="1">
<button x-h-step-indicator-trigger disabled>
<span x-h-step-indicator-marker>1</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Account</span>
<span x-h-step-indicator-description>Your details</span>
</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
<div x-h-step-indicator-item="2">
<button x-h-step-indicator-trigger>
<span x-h-step-indicator-marker>2</span>
<span x-h-step-indicator-content>
<span x-h-step-indicator-title>Address</span>
<span x-h-step-indicator-description>Shipping info</span>
</span>
</button>
<div x-h-step-indicator-separator></div>
</div>
</div>
</div>