@jarvis-ui
GitHub

Stepper

A keyboard-navigable multi-step flow with flexible triggers and panels.

Choose the domains that should share this renewal workflow.

Installation

npx shadcn@latest add https://ui.jarv.is/r/stepper.json

Usage

Basic flow

Use the stepper for multi-step forms, onboarding, and guided setup flows.

import { Stepper, StepperItem, StepperNav, StepperTrigger } from "@/components/ui/stepper";

export function Example() {
  return (
    <Stepper defaultValue={1}>
      <StepperNav>
        <StepperItem step={1}>
          <StepperTrigger>Profile</StepperTrigger>
        </StepperItem>
      </StepperNav>
    </Stepper>
  );
}

Step content

Pair StepperNav with StepperPanel and one StepperContent per step.

import {
  Stepper,
  StepperContent,
  StepperItem,
  StepperNav,
  StepperPanel,
  StepperTrigger,
} from "@/components/ui/stepper";

export function OnboardingStepper() {
  return (
    <Stepper defaultValue={1}>
      <StepperNav>
        <StepperItem step={1}>
          <StepperTrigger>Profile</StepperTrigger>
        </StepperItem>
        <StepperItem step={2}>
          <StepperTrigger>Team</StepperTrigger>
        </StepperItem>
      </StepperNav>
      <StepperPanel>
        <StepperContent value={1}>Profile form</StepperContent>
        <StepperContent value={2}>Team form</StepperContent>
      </StepperPanel>
    </Stepper>
  );
}

Controlled state

Control value when the active step is driven by form validation or route state.

"use client";

import { useState } from "react";

import { Stepper, StepperItem, StepperNav, StepperTrigger } from "@/components/ui/stepper";

export function ControlledStepper({ billingEnabled }: { billingEnabled: boolean }) {
  const [step, setStep] = useState(1);

  return (
    <Stepper value={step} onValueChange={setStep}>
      <StepperNav>
        <StepperItem step={1} completed={step > 1}>
          <StepperTrigger>Account</StepperTrigger>
        </StepperItem>
        <StepperItem step={2} disabled={!billingEnabled}>
          <StepperTrigger>Billing</StepperTrigger>
        </StepperItem>
      </StepperNav>
    </Stepper>
  );
}

Titles and indicators

Compose indicators, titles, descriptions, and separators for richer progress navigation.

import {
  Stepper,
  StepperDescription,
  StepperIndicator,
  StepperItem,
  StepperNav,
  StepperSeparator,
  StepperTitle,
  StepperTrigger,
} from "@/components/ui/stepper";

export function RichStepperNav() {
  return (
    <Stepper defaultValue={1}>
      <StepperNav>
        <StepperItem step={1} completed>
          <StepperTrigger>
            <StepperIndicator>1</StepperIndicator>
            <span className="flex flex-col items-start gap-1">
              <StepperTitle>Profile</StepperTitle>
              <StepperDescription>Owner details</StepperDescription>
            </span>
          </StepperTrigger>
          <StepperSeparator />
        </StepperItem>
        <StepperItem step={2}>
          <StepperTrigger>
            <StepperIndicator>2</StepperIndicator>
            <span className="flex flex-col items-start gap-1">
              <StepperTitle>Review</StepperTitle>
              <StepperDescription>Final check</StepperDescription>
            </span>
          </StepperTrigger>
        </StepperItem>
      </StepperNav>
    </Stepper>
  );
}

Vertical orientation

Use orientation="vertical" for narrow sidebars or long setup flows.

import { Stepper, StepperItem, StepperNav, StepperTrigger } from "@/components/ui/stepper";

export function VerticalStepper() {
  return (
    <Stepper orientation="vertical" defaultValue={2}>
      <StepperNav>
        <StepperItem step={1} completed>
          <StepperTrigger>Connect</StepperTrigger>
        </StepperItem>
        <StepperItem step={2}>
          <StepperTrigger>Configure</StepperTrigger>
        </StepperItem>
        <StepperItem step={3}>
          <StepperTrigger>Review</StepperTrigger>
        </StepperItem>
      </StepperNav>
    </Stepper>
  );
}