How to Add a Stepper to a React App
Step-by-step guide to add a step progress indicator to any React app with the Drivn CLI — a copy-paste, controlled Stepper with labels and icons.
Reaching for a stepper means your React app has a multi-step flow to signal — a checkout that moves through cart, shipping, payment, and review, or a signup wizard that walks through account, profile, and confirmation. You could hand-roll a row of numbered circles with a connecting line and manage the completed, active, and upcoming look yourself, but you would end up re-solving auto-numbering, the connector between steps, and the click-to-jump behaviour every time. The Stepper in Drivn packages all of that into one file you paste into your project.
Drivn installs by copy-paste: the CLI writes a stepper.tsx file into your repository. The component is a compound Stepper root plus a Stepper.Item child; you hand the root a step number and it derives each item's state by comparing that item's zero-based index against step. Because every indicator is a clickable <button> and the items read the current step through React context, the file opens with a 'use client' directive and renders inside a client boundary. Its only import past React is the Check icon from lucide-react for the completed state and the local cn utility.
This guide adds the Stepper to any React app — install the CLI, render your first stepper, drive the active step with state, and add labels, icons, and a vertical layout from the file you own. For the App Router walkthrough see the Next.js Stepper guide, and for copy-paste layouts see the Stepper examples.
Prerequisites
Before installing Stepper, confirm your React project has the two things Drivn assumes: Tailwind CSS v4 installed and processing your styles, and a @/ path alias pointing at your source directory — the component ships as a .tsx file, so TypeScript is part of the stack. If you scaffolded with create-next-app, npm create vite, or npx drivn@latest create, both are already wired; for a custom setup the installation page lists the minimal config. The Stepper's only third-party import is the Check icon from lucide-react, which renders inside a completed step, so make sure lucide-react is installed. The one detail worth knowing before you render it is that the Stepper is a Client Component: each indicator is a clickable button and the parts read the active step through React context, so its source opens with a 'use client' directive and renders inside a client boundary rather than straight in a Server Component.
Step 1 — Install Drivn via the CLI
Run the CLI from your project root to add the Stepper source file. The command prompts once for your install directory (defaulting to src/components/ui/) and writes a single stepper.tsx — no other files, because the component's only dependency past React is the cn utility and the Check icon. No global config file is created; the result is TypeScript source you edit like any other component. Confirm it landed in your editor, then commit it for a clean baseline. If your project uses a monorepo layout or a non-standard source path, the CLI docs cover the flags for targeting a custom location during install. Because the file is yours after install, future Drivn releases will not overwrite your edits — the whole control, from the index-injection logic to the styling object, is one file you can read top to bottom.
1 # add the Stepper source file 2 npx drivn add stepper
Step 2 — Render your first stepper
Import Stepper and render it inside a Client Component. The simplest form takes bare <Stepper.Item /> children with no props: the root walks its children with React.Children.toArray, injects each item's position as an internal _index, and an item with no label or icon renders that index plus one — so four items read as 1, 2, 3, 4. Pass the root a step number and the item at that index renders active, everything before it renders completed with a checkmark, and everything after renders upcoming. The example below shows a four-step stepper fixed at step 1. Because the file opens with 'use client', drop this into a client component or a page already inside a client boundary; the Next.js Stepper guide covers importing it into a Server Component page.
1 'use client' 2 3 import * as React from 'react' 4 import { Stepper } from '@/components/ui/stepper' 5 6 export function Wizard() { 7 const [step, setStep] = React.useState(1) 8 9 return ( 10 <Stepper step={step} onStepChange={setStep}> 11 <Stepper.Item /> 12 <Stepper.Item /> 13 <Stepper.Item /> 14 <Stepper.Item /> 15 </Stepper> 16 ) 17 }
Step 3 — Drive the active step with state
The Stepper has no internal step state — it renders exactly the step number you hand it, so the active step always lives in your component. To move through the flow, pair the Stepper with a couple of Button controls that increment and decrement the state, clamped to the number of steps. The onStepChange callback also fires with an item's index when a user clicks that step's indicator, so wiring it to your setter lets people jump directly to a step as well as page through with buttons. The example holds step in useState, advances it with Next and Back buttons, and disables each at the ends of the range. Because the value is yours, a form on the last step can read it, and a completed step's checkmark appears automatically once step passes that index.
1 'use client' 2 3 import * as React from 'react' 4 import { Stepper } from '@/components/ui/stepper' 5 import { Button } from '@/components/ui/button' 6 7 export function CheckoutSteps() { 8 const [step, setStep] = React.useState(0) 9 const last = 3 10 11 return ( 12 <div className="space-y-6"> 13 <Stepper step={step} onStepChange={setStep}> 14 <Stepper.Item label="Cart" /> 15 <Stepper.Item label="Shipping" /> 16 <Stepper.Item label="Payment" /> 17 <Stepper.Item label="Review" /> 18 </Stepper> 19 <div className="flex justify-between"> 20 <Button 21 variant="outline" 22 disabled={step === 0} 23 onClick={() => setStep((s) => s - 1)} 24 > 25 Back 26 </Button> 27 <Button 28 disabled={step === last} 29 onClick={() => setStep((s) => s + 1)} 30 > 31 Next 32 </Button> 33 </div> 34 </div> 35 ) 36 }
Step 4 — Add labels, icons, and a vertical layout
Each Stepper.Item takes three optional props. label puts a short text caption inside the indicator; icon accepts a lucide component reference — icon={User}, not a rendered <User /> — which the Stepper sizes for you; and disabled blocks clicks on that step. The indicator shows a checkmark once its step is complete, otherwise the label, then the icon, then the auto-number, in that order of precedence. On the root, orientation="vertical" stacks the items in a column with the connecting line running top to bottom, and line={false} drops the connectors entirely for a compact pager. Everything visible is a Tailwind class in the styles object inside the file you own, so recoloring a state is a one-line edit that every Stepper follows — see Theming for the tokens. The example shows an icon stepper and a vertical labelled stepper. For more copy-paste variants see the Stepper examples.
1 <Stepper step={1} onStepChange={setStep}> 2 <Stepper.Item icon={User} /> 3 <Stepper.Item icon={FileText} /> 4 <Stepper.Item icon={Send} /> 5 </Stepper> 6 7 <Stepper 8 step={2} 9 orientation="vertical" 10 onStepChange={setStep} 11 > 12 <Stepper.Item label="Account" /> 13 <Stepper.Item label="Profile" /> 14 <Stepper.Item label="Review" /> 15 <Stepper.Item label="Done" /> 16 </Stepper>
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
Frequently asked questions
Yes. Each step indicator is a clickable button and the Stepper.Item parts read the active step through React context, so the source opens with a 'use client' directive and renders inside a Client Component. In the Next.js App Router, put the Stepper — and the useState that holds the current step — in a file that starts with 'use client', then import it into your Server Component page. In a Vite or Create React App project the directive is simply ignored and the component works the same.
You tell it with the step prop. The Stepper has no internal step state — it derives each item's look by comparing that item's zero-based index against the step number you pass. An index below step renders completed, an index equal to step renders active, and anything above renders upcoming. Because the value lives in your component, another element — a form, a page counter, a Back and Next pair — can read and set the same number.
Yes. Each Stepper.Item takes a label prop for a text caption and an icon prop that accepts a lucide component reference like icon={User}. The indicator shows a checkmark once the step is complete, otherwise the label, then the icon, then the auto-number, in that order. Pass the icon as a reference, not a rendered element, and the Stepper sizes it for you — no <Icon /> boilerplate at the call site.
Wire the onStepChange callback to your step setter. The Stepper calls onStepChange with an item's index when the user clicks that step's indicator, so passing setStep lets people jump straight to a step by clicking its number or label. To restrict navigation, keep your own guard in the setter — ignore clicks on indices beyond the furthest completed step — or mark a step disabled to block clicks on it entirely.
Yes. The component depends only on React, the local cn utility, and lucide-react for the completed-step checkmark — no Next.js API and no router — so it works anywhere React and Tailwind reach the DOM. Set the project up with TypeScript and Tailwind v4 first, then run npx drivn add stepper and drive it the same way, owning the current step in useState.

