Steps
A controlled steps indicator with completed, current and upcoming states, and forward-jump locking for wizard flows
Installation
Usage
Basic Usage
Fully controlled index. With maxIndex set to the highest visited step, completed steps stay clickable for going back while future steps are locked (disabled) and cannot be skipped ahead — exactly what a wizard needs.
- AccountBasic info
- Details
- Settings
- Review
"use client";import * as React from "react";import { Steps } from "@/ui/components";const items = [ { title: "Account", description: "Basic info" }, { title: "Profile", description: "Details" }, { title: "Preferences", description: "Settings" }, { title: "Confirm", description: "Review" },];export default function StepsBasicDemo() { const [index, setIndex] = React.useState(0); return ( <div className="w-full space-y-6"> <Steps items={items} index={index} onChange={setIndex} /> <div className="flex items-center justify-between"> <button type="button" onClick={() => setIndex((i) => Math.max(0, i - 1))} disabled={index === 0} className="inline-flex h-8 cursor-pointer items-center gap-1 rounded-md border bg-background px-3 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-50" > Back </button> <span className="text-sm text-muted-foreground">Step {index + 1} of {items.length}</span> <button type="button" onClick={() => setIndex((i) => Math.min(items.length - 1, i + 1))} disabled={index === items.length - 1} className="inline-flex h-8 cursor-pointer items-center gap-1 rounded-md border bg-background px-3 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-50" > Next </button> </div> </div> );}Key points:
maxIndexdefaults toitems.length - 1(all steps clickable); pass the highest visited index to lock future steps.- Clicking a clickable step fires
onChange(nextIndex); the current step is never clickable. - States are derived:
completedshows aCheckicon and a primary connector line up to the current step,currentgetsaria-current="step".
Vertical
Set orientation="vertical" to stack the steps — titles and descriptions render beside the markers with a continuous connector line, matching the Timeline look.
- AccountCreate your login
- Tell us about you
- Review and submit
"use client";import * as React from "react";import { Steps } from "@/ui";const content = [ "Create your login credentials.", "Tell us a bit about yourself.", "Review everything and submit.",];export default function Demo() { const [index, setIndex] = React.useState(0); return ( <div className="w-full max-w-sm space-y-4"> <Steps orientation="vertical" index={index} onChange={setIndex} items={[ { title: "Account", description: "Create your login" }, { title: "Profile", description: "Tell us about you" }, { title: "Confirm", description: "Review and submit" }, ]} /> <div className="rounded-lg border p-3 text-sm text-muted-foreground">{content[index]}</div> <div className="flex items-center justify-between"> <button type="button" onClick={() => setIndex((i) => Math.max(0, i - 1))} disabled={index === 0} className="inline-flex h-8 cursor-pointer items-center rounded-md border bg-background px-3 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-50" > Back </button> <button type="button" onClick={() => setIndex((i) => Math.min(2, i + 1))} disabled={index === 2} className="inline-flex h-8 cursor-pointer items-center rounded-md border bg-background px-3 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-50" > Next </button> </div> </div> );}API Reference
Steps
| Prop | Type | Default | Description |
|---|---|---|---|
items | StepsItemConfig[] | - | Step definitions |
index | number | - | Controlled current step index |
onChange | (nextIndex: number) => void | - | Fired when a clickable step is selected |
maxIndex | number | items.length - 1 | Highest selectable index — steps beyond it are disabled |
orientation | "horizontal" | "vertical" | "horizontal" | "vertical" stacks the steps with titles beside the markers |
className | ClassNameValue | - | Custom class names, applied to the root |
classNames | marker / title / description / connector | - | Custom class names per slot |
StepsItemConfig
| Field | Type | Default | Description |
|---|---|---|---|
title | React.ReactNode | - | Step title |
description | React.ReactNode | - | Optional secondary text |
Composable Components
The parts extend native DOM props and merge their built-in className through cn — passing className extends the defaults instead of replacing them.
StepsRoot
The outer <ol> flex container (role list semantics come from the native element).
| Prop | Type | Default | Description |
|---|---|---|---|
className | ClassNameValue | - | Custom class names |
...props | React.ComponentProps<"ol"> | - | Native ol props |
StepsItem
A single step: connector line (from the second item on), circular marker button, title and optional description. The marker is a real <button> — keyboard accessible when clickable.
| Prop | Type | Default | Description |
|---|---|---|---|
index | number | - | Zero-based step index, reported through onSelect |
state | "completed" | "current" | "upcoming" | - | Visual state |
title | React.ReactNode | - | Step title |
description | React.ReactNode | - | Optional secondary text |
clickable | boolean | - | Whether the marker button is enabled |
onSelect | (index: number) => void | - | Fired on marker click |
className | ClassNameValue | - | Custom class names, applied to the item |
classNames | marker / title / description / connector | - | Custom class names per slot |
...props | React.ComponentProps<"li"> | - | Native li props |