Skip to content

Stepper

import { Stepper, type StepperItem } from '@yarcl/react';

Checkout progress

Choose an available stage, or complete it to continue.
Current stage: Details

Supply a named sequence of stages. Each id must be unique within that stepper. A const array infers valid identifiers for current state and callbacks, so unknown IDs produce a type error. Duplicate IDs throw at runtime.

const stages = [
{ id: 'details', label: 'Details', completed: true },
{ id: 'payment', label: 'Payment', description: 'Choose your payment method' },
{ id: 'review', label: 'Review', disabled: true },
] as const satisfies readonly StepperItem[];
<Stepper aria-label="Checkout" items={stages} defaultValue="payment"
onValueChange={(stage) => showStage(stage)} />

An uncontrolled stepper starts at its first enabled stage. Pass defaultValue for another initial stage. null represents no current stage, for example before starting or after completing the entire process. Changing defaultValue after mounting does not reset progress.

Labels and descriptions can include inline markup, but should contain no interactive content. A button’s accessible name is its label; its supporting description and completion status are announced separately. Step numbers follow the item order.

Own the current stage with value and onValueChange. Controlled components report requests and wait for your next prop update.

type Stage = 'details' | 'payment' | 'review';
const [stage, setStage] = useState<Stage | null>('payment');
<Stepper aria-label="Checkout" items={stages}
value={stage} onValueChange={setStage} />

completed is explicit on each item. It is independent of order and current state: returning to a completed stage does not erase completion, and moving ahead does not mark previous stages complete. Your application validates and completes its own stages. The stepper only displays progress and requests navigation; render forms or content alongside it.

The current stage uses aria-current="step". Completed stages show a check mark and expose translated completion text from labels.stepCompleted, defaulting to “Completed”. A stage can be both current and completed. Disabled stages remain visible, use native disabled buttons, and are skipped during keyboard focus movement.

Removing the current stage leaves no current marker until your state supplies an available ID. Focus recovers to an enabled stage when its item is removed or disabled. Updating items elsewhere does not steal focus from another control.

Use readOnly when a separate form or navigation flow controls progress. This renders a static ordered list with no step buttons. Current, completed and disabled information remains available to assistive technology.

<Stepper aria-label="Application status" readOnly value="review"
items={[
{ id: 'submitted', label: 'Submitted', completed: true },
{ id: 'review', label: 'Under review' },
{ id: 'decision', label: 'Decision pending' },
]} />

disabled prevents all interactive activation and removes every step button from the tab order. Switching a focused interactive stepper to static progress moves focus to its named list rather than losing the user’s location. An empty interactive sequence remains named and keyboard discoverable.

Indicators are connected by a timeline line. A completed stage colors its outgoing connector; current and disabled states remain independent of completion. No connector follows the final stage.

The default arrangement is horizontal. Set orientation="vertical" for a narrow sidebar or a detailed sequence with longer descriptions. Labels wrap within their available space.

Key Action
Left / Right Focus previous / next enabled horizontal stage, mirrored in RTL
Up / Down Focus previous / next enabled vertical stage
Home / End Focus first / last enabled stage
Enter / Space Activate the focused stage
Tab Enter at the current or last focused enabled stage, then leave the stepper

Focus movement does not change progress, and arrows stop at the edges. Native click and keyboard handlers run first; preventDefault() cancels activation or navigation. Native button behavior supplies Enter and Space activation. Stepper uses a named ordered list and the standard current step state. Its explicit list role preserves those semantics when custom marker styling is applied.

The typed radius prop shapes both indicators and interactive entries. The library defaults and bundled themes set components.Stepper.radius to the circular rounded radius token. An explicit radius overrides that component default.

<Stepper aria-label="Checkout" items={stages} radius="rounded" />
<Stepper aria-label="Checkout" items={stages} radius="md" />
<Stepper aria-label="Checkout" items={stages} radius="rounded" orientation="vertical" />

Use your own config keys. A circular token can have a large radius such as 9999px, while a normal rounded token can use your usual control radius. Configs with different names work equally well:

radii: { softCorner: '0.375rem', circle: '9999px' },
components: { Stepper: { radius: 'circle' } },

radius="size" resolves the radius named after the active size when available. Connector positions follow size tokens, responsive spacing, orientation and RTL. Long vertical descriptions wrap beside their indicator without shifting the timeline’s alignment. Runtime theme changes update the shape and line without resetting progress or focus.

size, radius and color are typed consumer config keys. gap accepts a spacing key or a mobile-first breakpoint map. Component defaults, allowed sizes and size overrides work as for other sized controls.

<Stepper aria-label="Checkout" items={stages}
gap={{ base: 'sm', lg: 'lg' }} />

Use the spacing names and breakpoints in your own config. Size controls indicator dimensions, typography and internal spacing; radius shapes indicators and buttons; color marks current and completed stages. Stepper renders classes with no inline token styles. The shared focus ring covers step buttons and the list when programmatically focused.

Runtime themes preserve progress, focus and DOM. Changing the label catalog updates completion copy immediately. Translate labels.stepCompleted by spreading the full default catalog, as described in labels and languages.

Native attributes, event handlers, className, style and React 19 ref reach the root ol. Style each entry through data-part="item", or use the item state attributes data-current, data-completed and data-disabled.

PropTypeDefaultDescription
items requiredreadonly StepperItem<V>[]Ordered stages, with unique identifiers.
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
colorColorconfigconfig.defaults.colorSemantic color, from the colors config.
defaultValueNoInfer<V> | nullfirst enabled itemInitially current stage when uncontrolled.
disabledbooleanPrevents all activation and removes step buttons from the tab order.
gapResponsive<Spacing>configconfig.defaults.gapSpace between stages, including typed responsive breakpoint overrides.
onValueChange(value: V) => voidCalled when an enabled stage is activated. Keyboard focus movement does not change the current stage.
orientation'horizontal' | 'vertical''horizontal'Arranges stages horizontally or vertically.
radius'size' | Radiusconfigconfig.defaults.radiusBorder radius, from the radii config, or 'size' for the radius named like the control's size.
readOnlybooleanDisplays static progress without interactive step buttons.
size'lg' | 'xs' | 'sm' | 'md' | 'xl'config.defaults.sizeControl size, from the sizes config.
valueNoInfer<V> | nullControlled current stage. null represents no current stage.