Skip to content

Stepper

beta

The Stepper component provides a vertical progress indicator for multi-step workflows like wizards and onboarding flows. It supports dynamic nested steps, multiple visual states, transition animations, navigation guards, and keyboard navigation.

It is composed of Stepper.Root, Stepper.StepGroup, and Stepper.Step to offer a structured and customizable interface.

Usage

import { Stepper } from "@harnessio/ui/components";
//...
const [currentStep, setCurrentStep] = useState("step-1");
return (
<Stepper.Root value={currentStep} onValueChange={setCurrentStep} title="Setup">
<Stepper.Step value="step-1" title="First Step" description="Do something" />
<Stepper.Step value="step-2" title="Second Step" description="Do another thing" />
<Stepper.Step value="step-3" title="Final Step" description="All done" />
</Stepper.Root>
)

Anatomy

All parts of the Stepper component can be imported and composed as required. A Stepper.Step rendered directly under Stepper.Root is a top-level step; a Stepper.Step rendered inside a Stepper.StepGroup is a nested step.

<Stepper.Root>
<Stepper.StepGroup>
<Stepper.Step />
<Stepper.Step />
</Stepper.StepGroup>
<Stepper.Step />
</Stepper.Root>

Step States

Steps display different visual states based on their position relative to the active step and explicit state overrides.

Loading and Blocking

Steps can show a loading spinner and optionally block forward navigation while an async operation is in progress.

Nested Steps

A Stepper.StepGroup can contain dynamic nested steps that appear when the group is active. Nested steps support conditional rendering based on application state.

Indeterminate Nested Steps

When a step group has hasNestedSteps but no children are provided, a placeholder indicator with ”…” is shown, signaling that nested steps will be determined dynamically (e.g., after an API call resolves).

Here’s an example combining real nested steps (.1, .2, .3) on the active step group with an indeterminate placeholder on a following step group:

Use the onBeforeChange prop to confirm navigation (e.g., warn users about losing progress when going back).

Completed State

When all steps are done, pass completed to show a “Complete” badge instead of the step counter.

Without Connectors

Set showConnectors={false} to hide the vertical connector lines between steps.

Skeleton Loading

When no children are passed, the stepper renders a skeleton placeholder. Control the number of skeleton items with skeletonCount.

API Reference

Root

The root container that manages stepper state, provides context to children, and renders the header with title and progress counter.

<Stepper.Root
value={currentStep} // active step or nested-step value
onValueChange={setStep} // callback when step changes
title="Setup" // [OPTIONAL] displayed in the header
completed={false} // [OPTIONAL] shows "Complete" instead of counter
showConnectors={true} // [OPTIONAL] show connector lines between steps
onBeforeChange={guardFn} // [OPTIONAL] navigation guard
skeletonCount={3} // [OPTIONAL] skeleton items when no children
className="my-class" // [OPTIONAL] custom class on the nav element
>
{/* Step / StepGroup components */}
</Stepper.Root>
Prop
Required
Default
Type
valuetruestring
onValueChangetrue(value: string) => void
titlefalsestring
completedfalsefalseboolean
showConnectorsfalsetrueboolean
onBeforeChangefalse(from: string, to: string) => boolean | string
skeletonCountfalse3number
classNamefalsestring
childrenfalseReactNode

StepGroup

A step that groups nested steps. Renders the indicator, title, description, connector, and optional nested Stepper.Step children.

<Stepper.StepGroup
value="connect" // unique identifier for this step group
title="Connect" // step title (string or ReactNode)
description="Choose provider" // [OPTIONAL] description text
state="error" // [OPTIONAL] explicit state override
loading={false} // [OPTIONAL] show spinner on indicator
blocking={false} // [OPTIONAL] block forward navigation
disabled={false} // [OPTIONAL] prevent interaction
hasNestedSteps={false} // [OPTIONAL] reserve nested-step space
className="my-class" // [OPTIONAL] custom class
>
{/* Optional Stepper.Step children (nested steps) */}
</Stepper.StepGroup>
Prop
Required
Default
Type
valuetruestring
titletruestring | ReactNode
descriptionfalsestring
statefalse'error' | 'skipped'
loadingfalsefalseboolean
blockingfalsefalseboolean
disabledfalsefalseboolean
hasNestedStepsfalsefalseboolean
classNamefalsestring
childrenfalseReactNode

Step

An individual step. Rendered directly under Stepper.Root, it behaves as a top-level step; rendered inside a Stepper.StepGroup, it behaves as a nested step and is only rendered when the parent step group is active.

<Stepper.Step
value="auth" // unique identifier for this step
title="Authenticate" // step title
description="OAuth with GitHub" // [OPTIONAL] description text
state="error" // [OPTIONAL] explicit state override
disabled={false} // [OPTIONAL] prevent interaction
className="my-class" // [OPTIONAL] custom class
/>
Prop
Required
Default
Type
valuetruestring
titletruestring | ReactNode
descriptionfalsestring
statefalse'error' | 'skipped'
disabledfalsefalseboolean
visualCompletedfalsefalseboolean
showStepBadgefalsefalseboolean
totalStepsOverridefalsenumber
classNamefalsestring

Accessibility

The stepper implements the following accessibility features:

  • ARIA: Uses aria-current="step" on the active step, aria-label on each step button with position context, and aria-disabled for disabled steps
  • Live Region: Announces step changes via aria-live="polite" for screen readers
  • Keyboard Navigation: Supports ArrowDown, ArrowUp, Home, and End keys with roving tabindex
  • Reduced Motion: Respects prefers-reduced-motion by disabling transition animations

Best Practices

  1. Unique Values: Every step and nested step must have a unique value across the entire stepper
  2. Controlled State: The component is always controlled — provide both value and onValueChange
  3. Blocking Steps: Use blocking for async operations that must complete before proceeding (e.g., connectivity checks)
  4. Navigation Guards: Use onBeforeChange to warn about data loss, not to enforce linear progression
  5. Nested Step Cleanup: When nested steps unmount (e.g., provider changes), the stepper automatically falls back to the parent step-group value
  6. Skeleton State: Pass no children to show a skeleton — useful while step configuration loads asynchronously