nili/ui
All components

StepIndicator

Overview
20 exports9 variant axes34 props4 files
import { Step, StepIndicator, StepIndicatorLabels, StepperDot } from '@nili/ui';

This is the library’s own overview page, rendered live. Switch the language or the theme in the header and it follows.

Horizontal

value is the index of the current step, 0-based. Everything before it reads as completed and everything after it as default — all three states follow from the position for free.

import { StepIndicator, Step } from "@nili/ui"

export function Example() {
  return (
    <StepIndicator value={1} labels={{ root: "Checkout" }}>
      <Step label="Account" />
      <Step label="Profile" />
      <Step label="Billing" />
      <Step label={t('step.done')} />
    </StepIndicator>
  )
}

With descriptions

A second line under the label. Keep it short — the indicator is a map of the flow, not the flow itself.

import { StepIndicator, Step } from "@nili/ui"

export function Example() {
  return (
    <StepIndicator value={2} labels={{ root: "Checkout" }}>
      <Step label="Account" description="Email and password" />
      <Step label="Profile" description="Name and photo" />
      <Step label="Billing" description="Card details" />
    </StepIndicator>
  )
}

Vertical

Same component, stacked. Right for a sidebar, or for a flow with long labels that would never fit across a row.

import { StepIndicator, Step } from "@nili/ui"

export function Example() {
  return (
    <StepIndicator orientation="vertical" value={1} labels={{ root: "Setup" }}>
      <Step label="Account" description="Email and password" />
      <Step label="Profile" description="Name and photo" />
      <Step label="Billing" description="Card details" />
    </StepIndicator>
  )
}

Revisitable steps

onSelect makes a step a real button — for a wizard whose earlier steps can be revisited. Without it the step is inert text, which is right when the flow is strictly forward.

import { StepIndicator, Step } from "@nili/ui"

export function Example() {
  const [current, setCurrent] = useState(1)

  return (
    <StepIndicator value={current} labels={{ root: "Checkout" }}>
      {steps.map((step, index) => (
        <Step
          key={step.label}
          label={step.label}
          onSelect={index <= current ? () => setCurrent(index) : undefined}
        />
      ))}
    </StepIndicator>
  )
}

Custom markers and forced status

icon replaces the number or the tick. status overrides the state derived from the indicator’s value — rarely needed, but there for a step that failed validation and must stay marked.

import { StepIndicator, Step, Icon } from "@nili/ui"
import { RiUser3Line, RiCheckLine } from "@remixicon/react"

export function Example() {
  return (
    <StepIndicator value={1} labels={{ root: "Setup" }}>
      <Step label={t('step.account')} icon={<Icon icon={RiCheckLine} />} />
      <Step label={t('step.profile')} icon={<Icon icon={RiUser3Line} />} />
      <Step label={t('step.billing')} status="completed" />
    </StepIndicator>
  )
}

Stepper Dot

The compact form: no labels, just position. For onboarding carousels and mobile sheets, where a labelled indicator would take the whole screen.

sm
xs
import { StepperDot } from "@nili/ui"

export function Example() {
  return (
    <>
      <StepperDot count={4} value={1} label="Onboarding" />
      <StepperDot count={4} value={1} size="xs" label="Onboarding" />
    </>
  )
}