Skip to content
    Style: Wireframe

    Components · Navigation

    Steps

    Progress through a flow: done, current (aria-current=step) and to come; horizontal when there is room.

    0.68% of the 2,073 sites in The UI Index have a stepper (14 sites).

    Used in UI Pro: Steps section

    Both previews render the same markup; the HTML tab below is that markup without a framework.

    Install

    React
    npx shadcn@latest add https://www.themes.dev/ui/r/steps.json
    Astro
    npx shadcn@latest add https://www.themes.dev/ui/r/astro/steps.json

    Or copy the files below into your project. They need clsx, tailwind-merge, and the themes.dev UI theme. The HTML needs only the compiled CSS.

    steps.tsx
    import { cn } from '@/lib/utils';
    import { CHECK_PATH, steps as c, type Step } from './steps.variants';
    
    export type { Step };
    
    export interface StepsProps {
      steps: Step[];
      /** The step you are on, counted from 0. Steps before it are done. */
      current: number;
      label?: string;
      className?: string;
    }
    
    /** Progress through a flow: done, current (aria-current="step") and still to come. */
    export function Steps({ steps, current, label = 'Progress', className }: StepsProps) {
      return (
        <nav aria-label={label} className={cn(c.root, className)}>
          <ol className={c.list}>
            {steps.map((s, k) => {
              const state = k < current ? 'done' : k === current ? 'current' : 'todo';
              return (
                <li key={k} data-state={state} aria-current={state === 'current' ? 'step' : undefined} className={c.item}>
                  <span className={c.rail}>
                    <span className={c.mark}>
                      {state === 'done' ? <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={CHECK_PATH} /></svg> : k + 1}
                    </span>
                    <span className={c.line} aria-hidden="true"></span>
                  </span>
                  <span className={c.text}>
                    <span className={c.title}>{s.title}<span className="sr-only">{state === 'done' ? ' (done)' : state === 'current' ? ' (current)' : ''}</span></span>
                    {s.description && <span className={c.description}>{s.description}</span>}
                  </span>
                </li>
              );
            })}
          </ol>
        </nav>
      );
    }
    steps.variants.ts
    /** Progress through a flow: an ordered list of steps, done (a check), current (aria-current="step", ink) and still
     *  to come. A real sequence, so the numbers are earned. Horizontal from a wide container, vertical on phones. */
    export const steps = {
      root: '@container w-full',
      list: 'm-0 grid list-none gap-4 p-0 @xl:flex @xl:items-start @xl:gap-0',
      item: 'group flex items-start gap-3 @xl:flex-1 @xl:flex-col @xl:gap-2',
      rail: 'flex items-center @xl:w-full',
      mark: 'grid size-7 shrink-0 place-items-center rounded-full border text-xs font-semibold tabular-nums transition-colors group-data-[state=done]:border-primary group-data-[state=done]:bg-primary group-data-[state=done]:text-primary-foreground group-data-[state=current]:border-2 group-data-[state=current]:border-primary group-data-[state=current]:text-foreground group-data-[state=todo]:border-input group-data-[state=todo]:text-muted-foreground [&_svg]:size-3.5',
      line: 'hidden h-px flex-1 bg-border group-data-[state=done]:bg-primary @xl:mx-2 @xl:block group-last:invisible',
      text: 'min-w-0 pt-0.5 @xl:pt-0 @xl:pr-4',
      title: 'block text-sm font-medium group-data-[state=todo]:text-muted-foreground',
      description: 'mt-0.5 block text-sm text-pretty text-muted-foreground',
    };
    export type Step = { title: string; description?: string };
    export const CHECK_PATH = 'M3.5 8.5l3 3 6-7';