Stepper
Stepper shows where someone is in a short sequence of steps. Use it for onboarding and for course generation.
Beta until Figma has a full Stepper (C3). The dots follow Figma Stepper Dot [1.1]; the list layout follows the live onboarding steps and is otherwise code-only. The inactive dot and connector colours are below 3:1 (decorative, state is in text). Everything on this page is implemented and tested.
Preview
Change the controls to try every supported combination. The preview renders the real component and the code updates with it.
- Your learning styleHow should we explain it?
- Your tutor, Not startedChoose how your tutor talks with you.
- Invite a friend, Not startedOptionalStudy together in a space.
import { Stepper, type StepperStep } from '@nuton/ui'
const steps: StepperStep[] = [
{ title: 'Your goal', description: 'What brings you here?' },
{ title: 'Your learning style', description: 'How should we explain it?' },
{ title: 'Your tutor', description: 'Choose how your tutor talks with you.' },
{ title: 'Invite a friend', description: 'Study together in a space.', optional: true },
]
<Stepper
steps={steps}
activeStep={1}
onStepSelect={setActiveStep}
aria-label="Set up your studio"
/>Installation
Availability
@nuton/ui and @nuton/tokens are private workspace packages. They aren't published to a registry yet (D4: registry deferred; D12: distribution waits on the provenance review). Inside the Nuton monorepo, add them as workspace dependencies. The commands below apply once the packages are published.
1. Add the packages
pnpm add @nuton/ui @nuton/tokens @remixicon/reactnpm install @nuton/ui @nuton/tokens @remixicon/reactyarn add @nuton/ui @nuton/tokens @remixicon/reactPeer dependencies: react and react-dom 18 or later. The check and cross come from @remixicon/react.
2. Load tokens and component styles once
// app/layout.tsx (or your app's entry file)
import '@nuton/tokens/tokens.css' // --nu-* design tokens
import '@nuton/ui/styles.css' // component styles (uses the tokens)Usage
import { Stepper } from '@nuton/ui'
const steps = [
{ title: 'Your goal' },
{ title: 'Your learning style' },
{ title: 'Your tutor' },
]
export function SetupSteps({ step }: { step: number }) {
return <Stepper steps={steps} activeStep={step} aria-label="Set up your studio" />
}activeStepis 0-based. Steps before it are complete, the step at it is current, later ones are upcoming.-1means nothing has started; a value past the end marks every step complete.- Set
statuson a step only to override that, usually'error'for a step that failed. - Give the list a name with
aria-label(default “Steps”) oraria-labelledby. - Pass
onStepSelectto let people go back: completed steps become buttons. Later steps never are. - Use 3 to 5 steps. For longer flows, use
variant="compact"or a Progress bar.
Variants and sizes
The default variant lists every step, horizontally or vertically. Compact and dots save space on phones and in modal footers.
Horizontal
- Your goal, CompletedWhat brings you here?
- Your learning styleHow should we explain it?
- Your tutor, Not startedChoose how your tutor talks with you.
<Stepper
steps={[
{ title: 'Your goal', description: 'What brings you here?' },
{ title: 'Your learning style', description: 'How should we explain it?' },
{ title: 'Your tutor', description: 'Choose how your tutor talks with you.' },
]}
activeStep={1}
aria-label="Set up your studio"
/>Steps share the width and connectors fill the space between them. When the stepper is narrower than 560px, only the current step shows its text, as in the live onboarding; the other names stay available to screen readers.
Vertical
- Your goal, CompletedWhat brings you here?
- Your learning styleHow should we explain it?
- Your tutor, Not startedChoose how your tutor talks with you.
<Stepper orientation="vertical" steps={steps} activeStep={1} aria-label="Set up your studio" />Numbers or icons
- Your goal, Completed
- Your learning style
- Your tutor, Not started
<Stepper
indicator="icon"
steps={[
{ title: 'Your goal', icon: <RiCompass3Line /> },
{ title: 'Your learning style', icon: <RiLightbulbLine /> },
{ title: 'Your tutor', icon: <RiUser3Line /> },
]}
activeStep={1}
aria-label="Set up your studio"
/>Complete steps always show a check and error steps a cross, whatever the indicator. Icons are decorative.
Compact
<Stepper variant="compact" steps={steps} activeStep={1} />“Step 2 of 3”, the current title and a Progress bar (code-only, for phones). It renders a named progressbar rather than a list; the bar turns red on an error and green when every step is done.
Dots
- Your goal
- Your learning style, Not started
- Your tutor, Not started
- Your goal, Completed
- Your learning style
- Your tutor, Not started
<Stepper variant="dots" steps={steps} activeStep={0} aria-label="Set up your studio" />
<Stepper variant="dots" dotSize="xs" steps={steps} activeStep={1} aria-label="Set up your studio" />Figma Stepper Dot [1.1], used in the Modal footer. Step names are visually hidden but still read as a list. Dots are never buttons (too small to press).
Dimensions
| Part | Size | Spacing | Type |
|---|---|---|---|
| Indicator | 24px circle, 1px ring; 16px icons | 8px to the text (horizontal), 12px (vertical) | Number Label/X Small 12 |
| Title | 20px line, centred on the indicator | — | Label/Small 14/20 |
| Description, Optional | 16px lines | Under the title | Paragraph/X Small 12/16 Sub-600 |
| Connector | 2px; at least 16px long (horizontal) | 8px each side (horizontal); 4px from indicators (vertical) | — |
| Vertical step | — | 24px between steps | — |
| Compact | Progress bar 6px | 2px between lines; 8px above the bar | Count Label/X Small 12/20; title Label/Medium 16/28 |
| Dots sm / xs | 8px / 4px | 10px / 6px apart | — |
Taken from the component CSS. Dots match Figma 365:20271; the rest is code-only (live onboarding uses a 21px indicator).
States and interactions
Each step is complete, current, upcoming or error. Any step can also be optional. The state is in hidden text as well as colour.
- Your goal, Completed
- Your learning style
aria-current="step".- Your tutor, Not started
- Upload sources, ErrorOne file couldn’t be read.
- Invite a friend, Not startedOptional
labels.optional).onStepSelect, completed steps are buttons: hover underlines the title, focus draws a 2px ring.Tokens
| Token | Default | Used for |
|---|---|---|
--nu-stepper-ring | State/Faded/Base | Upcoming indicator ring |
--nu-stepper-ring-current · --nu-stepper-bg-current · --nu-stepper-fg-current | State/Primary/Base · Primary/Lighter · Primary/Base | Current indicator |
--nu-stepper-fill-complete | State/Primary/Base | Complete indicator |
--nu-stepper-fill-error | State/Error/Base | Error indicator |
--nu-stepper-connector · --nu-stepper-connector-done | Stroke/Soft-200 · State/Primary/Base | Lines between steps |
--nu-stepper-dot · --nu-stepper-dot-current | Bg/Soft-200 · State/Primary/Base | Dots |
--nu-stepper-indicator-size | 24px | Indicator diameter |
Composition examples
Onboarding
The live setup dialog: Back and Continue move between steps, and completed steps are buttons. After each move, focus goes to the step heading so the change is announced.
Course generation
A vertical stepper that advances on its own. The stepper doesn't announce changes, so a role="status" line says what is happening.
Phones and translation
On small screens use the compact variant. Every string can be replaced through labels; stepOf is a function so word order can change.
API reference
Stepper props
| Prop | Type | Default | Description |
|---|---|---|---|
stepsrequired | StepperStep[] | — | The steps, in order. |
activeSteprequired | number | — | 0-based index of the current step. -1: nothing started. Past the end: all complete. |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Layout of the default variant. |
variant | 'default' | 'compact' | 'dots' | 'default' | Full list, “Step 2 of 4” + progress bar, or Figma dots. |
indicator | 'number' | 'icon' | 'number' | What upcoming and current indicators show. icon uses each step's icon. |
dotSize | 'sm' | 'xs' | 'sm' | Dot size for variant="dots" (Figma Size: Small, X-Small). |
onStepSelect | (index: number) => void | — | Makes completed steps buttons. Default variant only. |
labels | Partial<StepperLabels> | — | Overrides list (“Steps”), complete (“Completed”), upcoming (“Not started”), error (“Error”), optional (“Optional”) and stepOf (“Step 2 of 4”). |
aria-label | string | — | Names the list (or the compact progress bar). Defaults to labels.list. |
aria-labelledby | string | — | Names the list from a visible heading. |
ref | Ref<HTMLDivElement> | — | Forwarded to the root wrapper. |
…rest | Omit<HTMLAttributes<HTMLDivElement>, 'children'> | — | Passed to the root. |
StepperStep
| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | ReactNode | — | Step name. |
description | ReactNode | — | Line under the title. Not shown in dots. |
status | 'complete' | 'current' | 'upcoming' | 'error' | — | Overrides the state derived from activeStep. |
optional | boolean | — | Shows the “Optional” note. |
icon | ReactNode | — | Icon for indicator="icon". Falls back to the number. |
clickable | boolean | — | Whether the step is a button when onStepSelect is set. Default: steps before the active one that aren't upcoming. |
id | string | — | Stable key; defaults to the index. |
Also exported: getStepStatus(step, index, activeStep), STEPPER_LABELS and the types StepperStatus, StepperLabels.
Styling hooks
The root is .nu-stepper with data-variant, data-orientation, data-indicator and data-dot-size. Each li.nu-stepper__step has data-status. Override the tokens in Tokens.
Accessibility
Keyboard
| Key | Behaviour |
|---|---|
| Tab | Moves through clickable (completed) steps in order. Other steps are not focusable. |
| Enter / Space | Goes back to the focused completed step. |
Screen readers
An ordered list named “Steps” (or your aria-label), so position is announced (“2 of 4”). The current step has aria-current="step"; other steps end with hidden text such as “, Completed”. Indicators are hidden. Compact renders a progressbar named by the current title with the value text “Step 2 of 4”. Changes aren't announced: move focus or use a status message.
Focus
Clickable steps show a 2px focus ring (Highlight in forced colours). After going back or forward, move focus to the new step's heading.
Contrast and colour
Titles are Strong-950 or Sub-600 (6.69:1). The current number is Primary/Base on Primary/Lighter (4.94:1); checks and crosses are white on Primary (5.59:1) and Error (4.15:1). The upcoming ring is State/Faded/Base (4.23:1). Connectors and inactive dots (Soft-200, 1.19:1) are decorative: the state is always in text.
Touch and pointer
Clickable steps include the indicator and title, at least 24px tall. Dots are never interactive.
You must: name the stepper; keep step titles short and unique; announce progress or move focus when the step changes; describe errors in words.
Figma mapping
| Figma component · property | Figma values | Code |
|---|---|---|
| Stepper Dot [1.1] · State | 1st · 2nd · 3rd Active | variant="dots" + activeStep (any number of steps) |
| Stepper Dot [1.1] · Size | Small (8, gap 10) · X-Small (4, gap 6) | dotSize: sm · xs |
| Modal Footer [1.1] · Type=Stepper | Dots + Cancel / Continue | Compose a dots Stepper with Buttons in the Modal footer |
The full list, compact view, states and connectors have no Figma source (C3); they follow the live onboarding steps.
Guidelines and related components
Use Stepper for
- Short, ordered flows: onboarding, creating a space, setting up a quiz.
- Showing the phases of a long task, such as generating a course.
Don't use Stepper for
- Navigation between independent pages. Use Tabs or the sidebar.
- Long or open-ended progress, such as lessons in a course. Use Progress.
Do and don't
- Your goal, Completed
- Your learning style
- Your tutor, Not started
- Tell us what brings you to Nuton today, Completed
- Choose how you would like us to explain things
- Pick a tutor, Not started
- Upload sources, ErrorOne file couldn’t be read. Try a PDF.
- Generate outline, Not started
- Upload sources, Error
- Generate outline, Not started
Common mistakes
- Treating
activeStepas 1-based. The first step is 0. - Making future steps clickable with
clickable: truebefore earlier steps are valid. - Two steppers on one page with the same default name “Steps”. Give each an
aria-label. - Changing the step without moving focus or announcing it.