Stepper

Stepper shows where someone is in a short sequence of steps. Use it for onboarding and for course generation.

  • Beta
  • Accessibility
  • Figma Stepper Dot 365:20271
  • packages/ui/src/components/Stepper

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.

  1. Your learning styleHow should we explain it?
  2. Your tutor, Not startedChoose how your tutor talks with you.
  3. Invite a friend, Not startedOptionalStudy together in a space.

tsx
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

bash
pnpm add @nuton/ui @nuton/tokens @remixicon/react

Peer dependencies: react and react-dom 18 or later. The check and cross come from @remixicon/react.

2. Load tokens and component styles once

tsx
// 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

tsx
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" />
}
  • activeStep is 0-based. Steps before it are complete, the step at it is current, later ones are upcoming. -1 means nothing has started; a value past the end marks every step complete.
  • Set status on a step only to override that, usually 'error' for a step that failed.
  • Give the list a name with aria-label (default “Steps”) or aria-labelledby.
  • Pass onStepSelect to 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

  1. Your goal, CompletedWhat brings you here?
  2. Your learning styleHow should we explain it?
  3. Your tutor, Not startedChoose how your tutor talks with you.

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

  1. Your goal, CompletedWhat brings you here?
  2. Your learning styleHow should we explain it?
  3. Your tutor, Not startedChoose how your tutor talks with you.

Numbers or icons

  1. Your goal, Completed
  2. Your learning style
  3. Your tutor, Not started

Complete steps always show a check and error steps a cross, whatever the indicator. Icons are decorative.

Compact

Step 2 of 3Your learning style

“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

  1. Your goal
  2. Your learning style, Not started
  3. Your tutor, Not started
  1. Your goal, Completed
  2. Your learning style
  3. Your tutor, Not started

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

PartSizeSpacingType
Indicator24px circle, 1px ring; 16px icons8px to the text (horizontal), 12px (vertical)Number Label/X Small 12
Title20px line, centred on the indicator—Label/Small 14/20
Description, Optional16px linesUnder the titleParagraph/X Small 12/16 Sub-600
Connector2px; at least 16px long (horizontal)8px each side (horizontal); 4px from indicators (vertical)—
Vertical step—24px between steps—
CompactProgress bar 6px2px between lines; 8px above the barCount Label/X Small 12/20; title Label/Medium 16/28
Dots sm / xs8px / 4px10px / 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.

  1. Your goal, Completed
CompletePrimary fill with a check. Hidden text: “Completed”. Connector after it turns primary.
  1. Your learning style
CurrentPrimary ring on Primary/Lighter. aria-current="step".
  1. Your tutor, Not started
UpcomingFaded ring, Sub-600 title. Hidden text: “Not started”.
  1. Upload sources, ErrorOne file couldn’t be read.
ErrorError fill with a cross. Hidden text: “Error”. Say what went wrong in the description.
  1. Invite a friend, Not startedOptional
OptionalA visible “Optional” note under the title (labels.optional).
Try it in Composition
ClickableWith onStepSelect, completed steps are buttons: hover underlines the title, focus draws a 2px ring.

Tokens

TokenDefaultUsed for
--nu-stepper-ringState/Faded/BaseUpcoming indicator ring
--nu-stepper-ring-current · --nu-stepper-bg-current · --nu-stepper-fg-currentState/Primary/Base · Primary/Lighter · Primary/BaseCurrent indicator
--nu-stepper-fill-completeState/Primary/BaseComplete indicator
--nu-stepper-fill-errorState/Error/BaseError indicator
--nu-stepper-connector · --nu-stepper-connector-doneStroke/Soft-200 · State/Primary/BaseLines between steps
--nu-stepper-dot · --nu-stepper-dot-currentBg/Soft-200 · State/Primary/BaseDots
--nu-stepper-indicator-size24pxIndicator 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.

  1. Your goal
  2. Your learning style, Not started
  3. Your tutor, Not started

What brings you here?

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.

  1. Reviewing your sources, Not started
  2. Mapping the topic, Not started
  3. Defining the course architecture, Not started
  4. Sequencing chapters, Not started
  5. Drafting your first lesson, Not started

Not started

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.

Étape 2 sur 3Your learning style

API reference

Stepper props

Stepper props
PropTypeDefaultDescription
stepsrequiredStepperStep[]—The steps, in order.
activeSteprequirednumber—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.
labelsPartial<StepperLabels>—Overrides list (“Steps”), complete (“Completed”), upcoming (“Not started”), error (“Error”), optional (“Optional”) and stepOf (“Step 2 of 4”).
aria-labelstring—Names the list (or the compact progress bar). Defaults to labels.list.
aria-labelledbystring—Names the list from a visible heading.
refRef<HTMLDivElement>—Forwarded to the root wrapper.
…restOmit<HTMLAttributes<HTMLDivElement>, 'children'>—Passed to the root.

StepperStep

StepperStep fields
PropTypeDefaultDescription
titlerequiredReactNode—Step name.
descriptionReactNode—Line under the title. Not shown in dots.
status'complete' | 'current' | 'upcoming' | 'error'—Overrides the state derived from activeStep.
optionalboolean—Shows the “Optional” note.
iconReactNode—Icon for indicator="icon". Falls back to the number.
clickableboolean—Whether the step is a button when onStepSelect is set. Default: steps before the active one that aren't upcoming.
idstring—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

KeyBehaviour
TabMoves through clickable (completed) steps in order. Other steps are not focusable.
Enter / SpaceGoes 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 · propertyFigma valuesCode
Stepper Dot [1.1] · State1st · 2nd · 3rd Activevariant="dots" + activeStep (any number of steps)
Stepper Dot [1.1] · SizeSmall (8, gap 10) · X-Small (4, gap 6)dotSize: sm · xs
Modal Footer [1.1] · Type=StepperDots + Cancel / ContinueCompose 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

  1. Your goal, Completed
  2. Your learning style
  3. Your tutor, Not started
DoKeep step names short. Two or three words fit on one line and scan quickly.
  1. Tell us what brings you to Nuton today, Completed
  2. Choose how you would like us to explain things
  3. Pick a tutor, Not started
Don'tDon't write sentences as step names. Long names wrap and push the connectors apart.
  1. Upload sources, ErrorOne file couldn’t be read. Try a PDF.
  2. Generate outline, Not started
DoSay what went wrong. The error state needs words people can act on.
  1. Upload sources, Error
  2. Generate outline, Not started
Don'tDon't rely on the red indicator. A red circle alone doesn't say what happened or what to do next.

Common mistakes

  • Treating activeStep as 1-based. The first step is 0.
  • Making future steps clickable with clickable: true before 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.