Meet Frontfriend.Your brand. Your rules. Your agents, connected.Explore what’s new
DocumentationStepper

Stepper

A step-by-step process for users to navigate through a series of steps.

Interactive preview

Rendered from the shared Frontfriend stories with React Base, the same preview Studio shows. Styling comes from your Design System; the code below is native to Base UI.

Installation

Complete project setup first. Your configuration must select react/base and include your Design System ID.

pnpm exec frontfriend download stepper -f react --primitive base

Review dependencies reported by the downloader and keep your source changes in version control.

Usage

import {
  Stepper,
  StepperNav,
  StepperItem,
  StepperTrigger,
  StepperIndicator,
  StepperSeparator,
  StepperPanel,
  StepperContent,
} from "@/components/ui/stepper";
import * as React from "react";
export default function StepperExample() {
  const [value, setValue] = React.useState(2);
  return (
    <Stepper
      value={value}
      onValueChange={setValue}
      orientation="horizontal"
      className="w-80"
    >
      <StepperNav>
        {[1, 2, 3].map((step) => (
          <StepperItem key={step} step={step}>
            <StepperTrigger>
              <StepperIndicator>{step}</StepperIndicator>
            </StepperTrigger>
            {step < 3 && <StepperSeparator />}
          </StepperItem>
        ))}
      </StepperNav>
      <StepperPanel>
        {[1, 2, 3].map((step) => (
          <StepperContent key={step} value={step}>
            Step {step}
          </StepperContent>
        ))}
      </StepperPanel>
    </Stepper>
  );
}

Examples

Default

import {
  Stepper,
  StepperNav,
  StepperItem,
  StepperTrigger,
  StepperIndicator,
  StepperSeparator,
  StepperPanel,
  StepperContent,
} from "@/components/ui/stepper";
const steps = [1, 2, 3, 4];
<Stepper defaultValue={2} className="w-[420px] space-y-8">
  <StepperNav>
    {steps.map((step) => (
      <StepperItem key={step} step={step}>
        <StepperTrigger>
          <StepperIndicator>{step}</StepperIndicator>
        </StepperTrigger>
        {steps.length > step && <StepperSeparator />}
      </StepperItem>
    ))}
  </StepperNav>
  <StepperPanel className="text-sm">
    {steps.map((step) => (
      <StepperContent
        key={step}
        value={step}
        className="flex items-center justify-center"
      >
        Step {step} content
      </StepperContent>
    ))}
  </StepperPanel>
</Stepper>;

States

import {
  Stepper,
  StepperNav,
  StepperItem,
  StepperTrigger,
  StepperIndicator,
  StepperSeparator,
} from "@/components/ui/stepper";
import { Spinner } from "@/components/ui/spinner";
<Stepper
  defaultValue={3}
  indicators={{
    loading: <Spinner size="3" className="animate-spin text-onbrand-mid" />,
  }}
  className="w-[420px] space-y-8"
>
  <StepperNav>
    <StepperItem step={1} completed>
      <StepperTrigger>
        <StepperIndicator>1</StepperIndicator>
      </StepperTrigger>
      <StepperSeparator />
    </StepperItem>
    <StepperItem step={2}>
      <StepperTrigger>
        <StepperIndicator>2</StepperIndicator>
      </StepperTrigger>
      <StepperSeparator />
    </StepperItem>
    <StepperItem step={3} loading>
      <StepperTrigger>
        <StepperIndicator>3</StepperIndicator>
      </StepperTrigger>
      <StepperSeparator />
    </StepperItem>
    <StepperItem step={4} disabled>
      <StepperTrigger>
        <StepperIndicator>4</StepperIndicator>
      </StepperTrigger>
    </StepperItem>
  </StepperNav>
</Stepper>;

Controlled

import {
  Stepper,
  StepperNav,
  StepperItem,
  StepperTrigger,
  StepperIndicator,
  StepperSeparator,
  StepperPanel,
  StepperContent,
} from "@/components/ui/stepper";
import { Button } from "@/components/ui/button";
import * as React from "react";
const steps = [1, 2, 3, 4];
export default function StepperExample() {
  const [currentStep, setCurrentStep] = React.useState(2);
  return (
    <Stepper
      value={currentStep}
      onValueChange={setCurrentStep}
      className="w-[420px] space-y-8"
    >
      <StepperNav>
        {steps.map((step) => (
          <StepperItem key={step} step={step}>
            <StepperTrigger>
              <StepperIndicator>{step}</StepperIndicator>
            </StepperTrigger>
            {steps.length > step && <StepperSeparator />}
          </StepperItem>
        ))}
      </StepperNav>
      <StepperPanel className="text-sm">
        {steps.map((step) => (
          <StepperContent
            key={step}
            value={step}
            className="flex items-center justify-center"
          >
            Step {step} content
          </StepperContent>
        ))}
      </StepperPanel>
      <div className="flex items-center justify-between gap-2.5">
        <Button
          variant="tertiary"
          size="sm"
          onClick={() => setCurrentStep((step) => Math.max(1, step - 1))}
          disabled={currentStep === 1}
        >
          Previous
        </Button>
        <Button
          variant="tertiary"
          size="sm"
          onClick={() =>
            setCurrentStep((step) => Math.min(steps.length, step + 1))
          }
          disabled={currentStep === steps.length}
        >
          Next
        </Button>
      </div>
    </Stepper>
  );
}

Title & Description

import {
  Stepper,
  StepperNav,
  StepperItem,
  StepperTrigger,
  StepperIndicator,
  StepperTitle,
  StepperDescription,
  StepperSeparator,
} from "@/components/ui/stepper";
const titledSteps = [
  { title: "User Details", description: "Create your account" },
  { title: "Payment Info", description: "Add billing information" },
  { title: "Auth OTP", description: "Verify your identity" },
  { title: "Preview Form", description: "Review and finish" },
];
<Stepper defaultValue={2} className="w-[560px] space-y-8">
  <StepperNav>
    {titledSteps.map((step, index) => (
      <StepperItem
        key={step.title}
        step={index + 1}
        className="relative flex-1 items-start"
      >
        <StepperTrigger className="flex flex-col gap-2.5">
          <StepperIndicator>{index + 1}</StepperIndicator>
          <StepperTitle>{step.title}</StepperTitle>
          <StepperDescription>{step.description}</StepperDescription>
        </StepperTrigger>
        {titledSteps.length > index + 1 && (
          <StepperSeparator className="absolute left-[calc(50%+0.875rem)] right-[calc(-50%+0.875rem)] top-3 m-0 flex-none" />
        )}
      </StepperItem>
    ))}
  </StepperNav>
</Stepper>;

Vertical

import {
  Stepper,
  StepperNav,
  StepperItem,
  StepperTrigger,
  StepperIndicator,
  StepperTitle,
  StepperDescription,
  StepperSeparator,
} from "@/components/ui/stepper";
const titledSteps = [
  { title: "User Details", description: "Create your account" },
  { title: "Payment Info", description: "Add billing information" },
  { title: "Auth OTP", description: "Verify your identity" },
  { title: "Preview Form", description: "Review and finish" },
];
<Stepper
  defaultValue={2}
  orientation="vertical"
  className="w-[420px] space-y-8"
>
  <StepperNav>
    {titledSteps.map((step, index) => (
      <StepperItem
        key={step.title}
        step={index + 1}
        className="relative items-start not-last:flex-1"
      >
        <StepperTrigger className="items-start gap-2.5 pb-12 last:pb-0">
          <StepperIndicator>{index + 1}</StepperIndicator>
          <div className="mt-0.5 grid gap-1 text-left">
            <StepperTitle>{step.title}</StepperTitle>
            <StepperDescription>{step.description}</StepperDescription>
          </div>
        </StepperTrigger>
        {titledSteps.length > index + 1 && (
          <StepperSeparator className="absolute inset-y-0 left-3 top-7 -order-1 m-0 -translate-x-1/2 group-data-[orientation=vertical]/stepper-nav:h-[calc(100%-2rem)]" />
        )}
      </StepperItem>
    ))}
  </StepperNav>
</Stepper>;

API reference

Frontfriend-owned props. Native HTML and inherited primitive props remain documented by Base UI.

Stepper

Prop Type Default Description
defaultValue number 1
indicators StepIndicators {}
onValueChange (value: number) => void —
orientation StepperOrientation "horizontal"
value number —

StepperContent

Prop Type Default Description
forceMount boolean false
value number —

StepperItem

Prop Type Default Description
completed boolean false
disabled boolean false
loading boolean false
step number —

StepperTrigger

Prop Type Default Description
asChild boolean false

Configuration determines the actual classes and available styling hooks. See Theming & component configuration before introducing a repeated local override.