useStepper

Reference the hook for reading state and controlling navigation.

useStepper

Returns the flat runtime instance.

Example setup

All examples on this page use this stepper definition:

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

const checkout = defineStepper([
  { id: "shipping", title: "Shipping" },
  { id: "payment", title: "Payment" },
  { id: "review", title: "Review" },
]);
const stepper = checkout.useStepper();

Outside a provider, it creates local state. Inside the generated Provider or Stepper.Root, it reads the shared instance.

Use this hook for custom UI. Use Stepper.Root when you want the primitive components to create and share the same instance.

Options

Options customize one instance and override definition defaults.

OptionDescription
defaultStepInitial step for uncontrolled state.
step / onStepChangeControlled current step. step may be a raw external string; onStepChange receives known step ids.
onInvalidStepCalled when a controlled step is not a known step id.
data / onDataChangeControlled flow data.
completed / onCompletedChangeControlled completion state.
lineartrue restricts trigger and list keyboard affordances to adjacent/previous steps. Defaults to false.
beforeStepChangeGuard before navigation. Return false to cancel.
onStepChangeCalled after navigation succeeds (and to sync controlled step).
const stepper = checkout.useStepper({
  defaultStep: "payment",
  linear: true,
});

Controlled example:

const stepper = checkout.useStepper({
  step,
  onStepChange: setStep,
  data: values,
  onDataChange: setValues,
});

When an option is controlled, Stepperize calls the matching change callback instead of owning that piece of state internally.

Common reads

stepper.current; // current step object
stepper.id; // current step id
stepper.index; // zero-based index
stepper.count; // total steps
stepper.progress; // 0 to 1
stepper.canPrev;
stepper.canNext;
stepper.isPending;

Render current content

return stepper.match({
  shipping: () => <Shipping />,
  payment: () => <Payment />,
  review: () => <Review />,
});

match is exhaustive. Add a new step id, and TypeScript asks for a new handler.

Use is(id) for small fragments:

{stepper.is("review") && <Summary />}
stepper.next();
stepper.prev();
stepper.goTo("review");
stepper.reset();

Call navigation directly for simple UI. Await it when you need to know whether the move was accepted.

const accepted = await stepper.next({ data: formValues });

Each method resolves to true when the step changed.

false can mean the stepper is at an edge, the target is unknown, an async change is already running, the same step was requested without a value payload, or beforeStepChange cancelled the move. Note that imperative goTo is not gated by the linear policy — that policy only affects canGoTo, trigger affordances, and primitive list keyboard navigation.

Values

stepper.data.set({ email: "ada@example.com" });
stepper.data.set("profile", { name: "Ada" });

const current = stepper.data.get();
const all = stepper.data.all();

Payload values are visible to beforeStepChange and saved only when navigation succeeds. Inside that guard, ctx.validate() validates the step being left against the same pending data snapshot.

Lifecycle and effects

There are no imperative event subscriptions. Use the beforeStepChange guard and onStepChange option for flow behavior, and a plain effect for component-level reactions to the current step:

React.useEffect(() => {
  console.log("Moved to", stepper.id);
}, [stepper.id]);

Next: inspect the full stepper instance.

Edit on GitHub

Last updated on

On this page