React Stepper

Minimal React stepper with clear completed, current, and upcoming states for multi-step flows.

Add to your project

Pick the way you like to build.

Let your AI assistant add it

Copy this prompt and paste it into the AI tool building your site.

# Integrate this UI component

Add this component to my project.

1. Install any missing dependencies.
2. Make sure Tailwind CSS is set up and scans the new file.
3. Prefer passing props over editing the component source — changing the component itself may break it.
4. Adjust colors, size, and layout via props or wrapper classes to match my project. You know my codebase better than this snippet does.

## Component source

```tsx
"use client";

import type { ReactNode } from "react";

export type StepperStep = {
  title: string;
  description?: string;
  icon?: ReactNode;
};

type ReactStepperProps = {
  steps: StepperStep[];
  currentStep: number;
  // eslint-disable-next-line no-unused-vars
  onStepChange?: (stepIndex: number) => void;
  ariaLabel?: string;
  className?: string;
};

export default function ReactStepper({
  steps,
  currentStep,
  onStepChange,
  ariaLabel = "Progress",
  className = "",
}: ReactStepperProps) {
  if (!steps.length) return null;

  const activeStep = Math.min(Math.max(currentStep, 0), steps.length - 1);

  return (
    <nav
      aria-label={ariaLabel}
      className={["w-full", className].filter(Boolean).join(" ")}
    >
      <ol className="flex w-full">
        {steps.map((step, index) => {
          const complete = index < activeStep;
          const current = index === activeStep;
          const marker = complete ? (
            <svg
              viewBox="0 0 20 20"
              className="h-4 w-4"
              fill="none"
              aria-hidden="true"
            >
              <path
                d="m5.5 10 3 3 6-6"
                stroke="currentColor"
                strokeWidth="1.8"
                strokeLinecap="round"
                strokeLinejoin="round"
              />
            </svg>
          ) : (
            (step.icon ?? <span>{index + 1}</span>)
          );
          const markerClassName = [
            "relative z-10 flex h-10 w-10 items-center justify-center rounded-full border text-sm font-semibold transition-colors",
            complete
              ? "border-indigo-600 bg-indigo-600 text-white"
              : current
                ? "border-indigo-600 bg-white text-indigo-600 shadow-[0_0_0_4px_rgb(238_242_255)]"
                : "border-gray-200 bg-white text-gray-400",
          ].join(" ");

          return (
            <li
              key={`${step.title}-${index}`}
              aria-current={current ? "step" : undefined}
              className="relative min-w-0 flex-1 last:flex-none"
            >
              {index < steps.length - 1 ? (
                <span
                  aria-hidden="true"
                  className={[
                    "absolute left-10 right-0 top-5 h-px",
                    complete ? "bg-indigo-600" : "bg-gray-200",
                  ].join(" ")}
                />
              ) : null}

              <div className="relative flex flex-col items-start pr-3 sm:pr-8">
                {complete && onStepChange ? (
                  <button
                    type="button"
                    onClick={() => onStepChange(index)}
                    aria-label={`Go back to ${step.title}`}
                    className={`${markerClassName} focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2`}
                  >
                    {marker}
                  </button>
                ) : (
                  <span className={markerClassName} aria-hidden="true">
                    {marker}
                  </span>
                )}

                <span
                  className={[
                    "mt-3 truncate text-sm font-semibold",
                    current || complete ? "text-gray-950" : "text-gray-400",
                  ].join(" ")}
                >
                  {step.title}
                </span>
                {step.description ? (
                  <span className="mt-1 hidden max-w-40 text-xs leading-5 text-gray-500 sm:block">
                    {step.description}
                  </span>
                ) : null}
                {current ? <span className="sr-only">Current step</span> : null}
              </div>
            </li>
          );
        })}
      </ol>
    </nav>
  );
}

```

Works with your AI tools

ClaudeClaude*
CodexCodex*
LovableLovable
Base44Base44
Figma MakeFigma Make
ReplitReplit*
CursorCursor*
v0v0

* Compatible with most projects that use the default React/Next.js framework.

Built With

ReactTailwind CSS

Props

PropTypeDescription
stepsStepperStep[]Step titles with optional descriptions and icons
currentStepnumberZero-based active step index
onStepChange(index: number) => voidCalled when a completed step is selected
ariaLabelstringAccessible label for the progress navigation
classNamestringStyles for the outer navigation