defineStepper

Reference the API for defining typed flows.

defineStepper

Creates one typed flow.

import { defineStepper } from "@stepperize/react";

const checkout = defineStepper(
  [
    { id: "shipping", title: "Shipping" },
    { id: "payment", title: "Payment" },
    { id: "review", title: "Review" },
  ],
  {
    defaultStep: "shipping",
    linear: true,
  },
);

Use one definition per flow: checkout, onboarding, survey, setup wizard.

The definition is static. It does not create React state until you call useStepper, render Provider, or render Stepper.Root.

Parameters

ParameterDescription
stepsOrdered step objects. Must contain at least one step, and each step needs a unique id; every other field is yours.
options.defaultStepInitial step id. Defaults to the first step.
options.defaultDataInitial flow data.
options.defaultCompletedInitial completed step ids.
options.linearfalse by default. Set true to restrict trigger and list keyboard affordances to adjacent/previous steps.

Definition options become defaults for every instance created from this definition. Lifecycle callbacks (beforeStepChange, onStepChange) are instance-only — pass them to useStepper, Provider, or Stepper.Root.

Duplicate literal ids are caught by TypeScript when the step list is a literal tuple. Stepperize also validates ids at runtime, so dynamic or widened step arrays still fail early with a clear error.

Returns

PropertyDescription
stepsOriginal steps in order.
useStepper(options?)Hook that returns the flat instance.
ProviderContext provider for one shared instance.
StepperBound unstyled primitives.
get(id)Pure step access by id.
at(index)Pure step access by index.
parseStep(value)Narrow an arbitrary value to a known step id, or undefined.
validate(id, value)Validate an arbitrary value against a step's schema (Standard Schema). Schemaless steps always succeed.

Local instance

function Checkout() {
  const stepper = checkout.useStepper();
  return <h2>{stepper.current.title}</h2>;
}

Shared instance

function Page() {
  return (
    <checkout.Provider defaultStep="shipping">
      <CurrentTitle />
      <Actions />
    </checkout.Provider>
  );
}

function CurrentTitle() {
  const stepper = checkout.useStepper();
  return <h2>{stepper.current.title}</h2>;
}

Step access without state

const first = checkout.at(0);
const payment = checkout.get("payment");

Use these in static config, tests, or setup code. Use instance step access helpers inside running UI.

Choosing defaults

const checkout = defineStepper(steps, {
  defaultStep: "shipping",
  defaultData: {
    shipping: savedShippingDraft,
  },
  defaultCompleted: ["shipping"],
  linear: true,
});

Use definition defaults for the normal behavior of the flow. Use useStepper(options), Provider props, or Stepper.Root props for a specific screen or controlled integration.

Common mistakes

MistakeFix
Defining steps inside a component on every renderMove static definitions outside the component.
Passing an empty step arrayDefine at least one step. defineStepper throws early for empty arrays.
Reusing a step idGive every step a unique id. Literal duplicates are type errors when possible; runtime validation catches the rest.
Using labels as idsUse stable ids like "shipping" and keep labels in title.
Expecting defaultStep to control state after mountUse controlled step / onStepChange for external state.
Marking all previous steps complete automaticallyUse setComplete(id) when your app accepts a step.
Edit on GitHub

Last updated on

On this page