Stepper — <mv-stepper>
Horizontal or vertical steps (completed, current, upcoming) with connectors that fill with the accent color, automatic numbers, an animated checkmark and a loading step; optional <mv-stepper value> to drive the states.
Category Navigation Type Web Component (<mv-stepper>) Status stable Keywords stepper, steps, étapes, wizard, multi-step, progress, timeline, checkout
When to use
A multi-step flow such as onboarding or a wizard must show completed, current and upcoming steps A vertical list of stages shows the progress of an order, a deployment or a setup process A linear wizard must prevent jumping ahead to steps not reached yet
Avoid when
The sections are independent and can be visited in any order → use Tabs instead Progress is a single percentage without named stages → use Progress instead
Get Stepper. Included in every plan: the Pro pack ships all 226 components with the CLI and the MCP server used below.
See plans
Install
node scripts/add.mjs stepper --out ./src/marvelous
AI agent with the Marvelous UI MCP server: install_components({ slugs: ["stepper"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, components/stepper/stepper.css, components/stepper/stepper.js.
Usage
Canonical markup — start from it and customize with attributes, data-* and CSS variables:
<div style="display:grid;gap:2.5rem;width:100%;max-width:46rem;margin-inline:auto">
<!-- Horizontal, controlled, clickable (linear) -->
<div style="display:grid;gap:1.25rem">
<mv-stepper id="st-demo-checkout" value="2" linear>
<ol class="mv-stepper" aria-label="Checkout steps">
<li class="mv-stepper-item"><button class="mv-stepper-trigger"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Cart</span></span></button></li>
<li class="mv-stepper-item"><button class="mv-stepper-trigger"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Shipping</span></span></button></li>
<li class="mv-stepper-item"><button class="mv-stepper-trigger"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Payment</span></span></button></li>
<li class="mv-stepper-item"><button class="mv-stepper-trigger"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Confirmation</span></span></button></li>
</ol>
</mv-stepper>
<div style="display:flex;justify-content:space-between;align-items:center;gap:.75rem">
<button class="mv-button" data-variant="outline" data-size="sm" id="st-demo-prev">Back</button>
<span id="st-demo-label" style="font-size:.8125rem;color:var(--mv-fg-muted)" aria-live="polite">Step 2 of 4</span>
<button class="mv-button" data-size="sm" id="st-demo-next">Continue</button>
</div>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr));gap:2.5rem;align-items:start">
<!-- Vertical with descriptions and a busy step -->
<ol class="mv-stepper" data-orientation="vertical" aria-label="Package tracking">
<li class="mv-stepper-item" data-state="completed">
<span class="mv-stepper-indicator"></span>
<span class="mv-stepper-text"><span class="mv-stepper-title">Order confirmed</span><span class="mv-stepper-description">Mon, Sep 21, 2:32 PM</span></span>
</li>
<li class="mv-stepper-item" data-state="completed">
<span class="mv-stepper-indicator"></span>
<span class="mv-stepper-text"><span class="mv-stepper-title">Package shipped</span><span class="mv-stepper-description">Chicago warehouse · UPS</span></span>
</li>
<li class="mv-stepper-item" data-state="current" aria-current="step" aria-busy="true">
<span class="mv-stepper-indicator"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"/><path d="M15 18H9"/><path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.62l-3.48-4.35A1 1 0 0 0 17.52 8H14"/><circle cx="17" cy="18" r="2"/><circle cx="7" cy="18" r="2"/></svg></span>
<span class="mv-stepper-text"><span class="mv-stepper-title">Out for delivery</span><span class="mv-stepper-description">Estimated arrival tomorrow by 1 PM</span></span>
</li>
<li class="mv-stepper-item" data-state="upcoming">
<span class="mv-stepper-indicator"></span>
<span class="mv-stepper-text"><span class="mv-stepper-title">Delivered</span><span class="mv-stepper-description">Signature required</span></span>
</li>
</ol>
<!-- Dots, CSS only -->
<div style="display:grid;gap:1.5rem">
<ol class="mv-stepper" data-variant="dots" aria-label="Account setup">
<li class="mv-stepper-item" data-state="completed"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Profile</span></span></li>
<li class="mv-stepper-item" data-state="current" aria-current="step"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Team</span></span></li>
<li class="mv-stepper-item" data-state="upcoming"><span class="mv-stepper-indicator"></span><span class="mv-stepper-text"><span class="mv-stepper-title">Done</span></span></li>
</ol>
<ol class="mv-stepper" aria-label="File import" style="--mv-stepper-size:1.75rem;--mv-stepper-accent:var(--mv-success)">
<li class="mv-stepper-item" data-state="completed"><span class="mv-stepper-indicator"></span></li>
<li class="mv-stepper-item" data-state="completed"><span class="mv-stepper-indicator"></span></li>
<li class="mv-stepper-item" data-state="current" aria-current="step"><span class="mv-stepper-indicator"></span></li>
<li class="mv-stepper-item" data-state="upcoming"><span class="mv-stepper-indicator"></span></li>
<li class="mv-stepper-item" data-state="upcoming"><span class="mv-stepper-indicator"></span></li>
</ol>
</div>
</div>
</div>
<script type="module">
const stepper = document.getElementById("st-demo-checkout");
const label = document.getElementById("st-demo-label");
const prev = document.getElementById("st-demo-prev");
const next = document.getElementById("st-demo-next");
const sync = (v) => {
label.textContent = v > 4 ? "Order placed" : `Step ${v} of 4`;
prev.disabled = v <= 1;
next.disabled = v > 4;
next.textContent = v >= 4 ? "Place order" : "Continue";
};
prev?.addEventListener("click", () => stepper.prev());
next?.addEventListener("click", () => stepper.next());
stepper?.addEventListener("mv-change", (e) => sync(e.detail.value));
if (stepper) sync(Number(stepper.getAttribute("value")));
</script>
API
Attributes
Name Type Default Description valuenumber 1On <mv-stepper>: current step (1-based). Previous steps become completed; step count + 1 = all completed. linearboolean On <mv-stepper>: steps after the current one are not clickable. data-statecompleted | current | upcoming On .mv-stepper-item (set by <mv-stepper>, or by hand for pure CSS). data-orientationhorizontal | vertical horizontalOn .mv-stepper: layout and connector direction. data-variantdots On .mv-stepper: indicators shrunk to dots. aria-busytrue On a step: loading ring around the indicator.
Properties
Name Type Description countnumber Number of steps (read-only).
Methods
Name Description goTo(step)Goes to a step and emits mv-change. next() / prev()Next / previous step.
Events
Name Description mv-changeCancelable. detail: { value, previous, completed }.
CSS classes
Name Description mv-stepperOn <ol aria-label>. mv-stepper-itemA step (<li>). mv-stepper-indicatorBadge: automatic number when empty, icon otherwise, checkmark when completed. mv-stepper-text / -title / -descriptionStep labels. mv-stepper-trigger<button> wrapping the indicator and text to make the step clickable.
CSS variables
Name Default Description --mv-stepper-accentvar(--mv-accent)Color of completed/current steps and of the fill. --mv-stepper-size2remIndicator diameter.
Accessibility
Labeled ordered list; the current step has aria-current="step". Numbers and the checkmark are decorative (the title carries the meaning); add hidden text (.mv-sr-only “completed”) if the state must be announced. Clickable steps are real <button>s, disabled past the current one in linear mode. Reduced motion: no loading spin, instant fills.