Hook

Access and control your stepper with the useStepper hook

The useStepper hook returns the stepper instance with a structured API: state, navigation, lookup, flow, metadata, and lifecycle. Inside <Scoped> or Stepper.Root, useStepper() (with no arguments) reads from context. In a component with no provider, useStepper() creates its own stepper state for that component. You can still pass config when outside a provider to set initialStep and initialMetadata.

The hook accepts an optional single argument: a config object with:

  • initialStep — The ID of the initial step (must be one of your step IDs).
  • initialMetadata — Partial record of step ID → metadata; used to seed metadata.get(id) and state.current.metadata per step.

Usage

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "first", : "First step" },
  { : "second", : "Second step" }
);

const  = () => {
  const  = ();
  return (
    <>
      <>{....}</>
      < ={() => ..()} ={..}>Next</>
      < ={() => ..()} ={..}>Previous</>
    </>
  );
};

Rendering methods

The stepper exposes flow with helpers to render content by step: flow.is, flow.when, flow.switch, and flow.match.

when

The flow.when method allows rendering content conditionally based on the current step. It takes a step ID (string or array of ID plus conditions), a whenFn (the function to run if the step matches), and an optional elseFn (run when it does not match).

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "first", : "First step" },
  { : "second", : "Second step" }
);

const  = () => {
  const  = ();
  return (
    <>
      {..("first", () => (
        <>First step: {.}</>
      ))}
      {..("second", () => (
        <>Second step: {.}</>
      ))}
    </>
  );
};

You can pass an optional elseFn to render when the current step does not match:

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "info", : "Info" },
  { : "review", : "Review" },
  { : "done", : "Done" }
);

function () {
  const  = ();
  return (
    <>
      {..(
        "review",
        () => <>You're on review: {.}</>,
        () => <>Other step: {.}</>
      )}
    </>
  );
}

You can also use an array as the first argument: the first element is the step ID, the rest are boolean conditions. The step matches only when the ID matches and all conditions are true. This allows multi-condition logic (e.g. step + feature flag or validation state).

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "first", : "First step" },
  { : "second", : "Second step" }
);

const  = () => {
  const  = ();
  const  = true;
  const  = true;
  return (
    <>
      {..(["first", , ], () => (
        <>First step (editable): {.}</>
      ))}
      {..("second", () => (
        <>Second step: {.}</>
      ))}
    </>
  );
};

switch

The flow.switch method lets you render content based on the current step ID in a switch-case style. It is a cleaner and more scalable way to handle many steps without multiple when conditions.

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "first", : "First step" },
  { : "second", : "Second step" }
);

const  = () => {
  const  = ();
  return (
    <>
      {..({
        : () => <>First: {.}</>,
        : () => <>Second: {.}</>,
      })}
    </>
  );
};

match

The flow.match method lets you render content based on an external step ID (e.g. from the server, URL, or other state). Unlike flow.switch, which uses the stepper’s current step, flow.match takes that ID as the first argument. Useful for frameworks like Remix with server-side state or when driving the UI from a URL param.

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "first", : "First step" },
  { : "second", : "Second step" }
);

const  = () => {
  const  = ();
  const  = "first"; // e.g. from server or URL
  return (
    <>
      {..(, {
        : () => <>First: {.}</>,
        : () => <>Second: {.}</>,
      })}
    </>
  );
};

is

For simple conditionals, use flow.is(id). It returns true when the current step’s ID equals id.

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "form", : "Form" },
  { : "confirmation", : "Confirmation" }
);

function () {
  const  = ();
  return (
    <>
      {..("confirmation") && <>Order summary here.</>}
      {..("form") && <>Fill the form.</>}
    </>
  );
}

State and navigation

Property / MethodDescription
state.allArray of all step objects
state.current{ data, index, status, metadata } — current step; use state.current.metadata.get/set/reset for current step only
state.isFirst / state.isLasttrue when on first or last step
state.isTransitioningtrue while a transition (with lifecycle callbacks) is in progress
navigation.next()Go to next step
navigation.prev()Go to previous step
navigation.goTo(id)Go to step by ID
navigation.reset()Reset to initial step
import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "info", : "Info" },
  { : "review", : "Review" },
  { : "done", : "Done" }
);

function () {
  const  = ();
  return (
    <>
      <>Step {... + 1}: {....}</>
      < ="button" ={() => ..()} ={..}>Back</>
      < ="button" ={() => ..()} ={..}>Next</>
      < ="button" ={() => ..("review")}>Jump to Review</>
      < ="button" ={() => ..()}>Reset</>
    </>
  );
}

Lookup

Step lookup helpers (same shape as generateStepperUtils from @stepperize/core):

MethodDescription
lookup.getAll()All steps
lookup.get(id)Step object by ID
lookup.getIndex(id)Index of step by ID
lookup.getByIndex(index)Step at index
lookup.getFirst() / lookup.getLast()First / last step
lookup.getNext(id) / lookup.getPrev(id)Next / previous step after id
lookup.getNeighbors(id){ prev, next } for step id
import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "info", : "Info" },
  { : "review", : "Review" },
  { : "done", : "Done" }
);

function () {
  const  = ();
  const  = ..("review");
  const  = ..(....);
  return (
    <>Review: {?.}; neighbors: {.?. ?? "none"} → {.?. ?? "none"}</>
  );
}

Transition hooks (before / after)

For actions before or after a transition (e.g. validation, analytics), use stepper.lifecycle.onBeforeTransition(cb) and stepper.lifecycle.onAfterTransition(cb). They run on every next(), prev(), and goTo(). Return false (or a promise that resolves to false) from onBeforeTransition to cancel the transition.

The callback receives a TransitionContext with: from, to, metadata, statuses, direction ("next" | "prev" | "goTo"), fromIndex, toIndex. Use direction to branch (e.g. run logic only before "next" or "prev").

onBeforeTransition

Runs before the step index updates. Return false to cancel.

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "first", : "First" },
  { : "second", : "Second" }
);

function () {
  const  = ();
  React.(() => {
    ..(async () => {
      if (. === "prev" && !.("Go back?")) return false;
    });
  }, []);
  return (
    < ="button" ={() => ..()} ={..}>
      Next
    </>
  );
}

onAfterTransition

Runs after the step index has updated.

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "first", : "First" },
  { : "second", : "Second" }
);

function () {
  const  = ();
  React.(() => {
    ..(() => {
      .(`${..} → ${..}`);
    });
  }, []);
  return (
    < ="button" ={() => ..()} ={..}>
      Next
    </>
  );
}

Metadata

Metadata lets you attach custom data per step (e.g. form drafts, values from the server). You can set initial metadata in the hook config and read/write it at runtime.

Initial metadata

Pass initialMetadata in useStepper({ ... }) to seed metadata per step:

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "first", : "First step" },
  { : "second", : "Second step" }
);

const  = () => {
  const  = ({
    : {
      : { : "1" },
    },
  });
  const  = ..("first");
  return <>First step: {?.value}</>;
};

set / get / reset

  • metadata.set(id, values) — Set metadata for a step.
  • metadata.get(id) — Get metadata for a step.
  • metadata.reset(keepInitialMetadata?) — Reset all metadata; pass true to keep initial values from config.

Current step only: state.current.metadata.get(), state.current.metadata.set(values), state.current.metadata.reset().

import * as React from "react";
import {  } from "@stepperize/react";

const {  } = (
  { : "first", : "First step" },
  { : "second", : "Second step" },
  { : "last", : "Last step" }
);

const  = () => {
  const  = ();
  const  = () => {
    ..("first", { : "saved" });
  };
  const  = ..("first");
  return (
    <>
      < ="button" ={}>Save</>
      <>First step: {?.value}</>
      < ="button" ={() => ..(true)}>Reset metadata</>
    </>
  );
};

API reference

NameTypeDescription
stateStepperState<Steps>all, current (data, index, status, metadata), isFirst, isLast, isTransitioning
navigationStepperNavigation<Steps>next(), prev(), goTo(id), reset()
lookupStepperLookup<Steps>getAll(), get(id), getIndex(id), getByIndex(index), getFirst() / getLast(), getNext(id) / getPrev(id), getNeighbors(id)
flowStepperFlow<Steps>is(id), when(id, whenFn, elseFn?), switch(when), match(state, matches)
metadataStepperMetadata<Steps>values, set(id, values), get(id), reset(keepInitialMetadata?)
lifecycle{ onBeforeTransition, onAfterTransition }Register callbacks for every transition; return false from onBeforeTransition to cancel
Edit on GitHub

Last updated on

On this page