Scoped

Build a stepper with Scoped and useStepper (single or nested multi-scoped) without primitives.

Use Scoped and useStepper() so any descendant can read and control the same stepper—no primitives, just your own UI. You can use a single Scoped (one stepper) or nest two or more Scoped providers (e.g. global wizard + local sub-steps). The tabs below switch between the single-scope and multi-scoped patterns.

Define your steps (and get Scoped / useStepper)

Use defineStepper with one object per step. For a single stepper you destructure Scoped and useStepper. For multi-scoped you create two (or more) steppers; each has its own Scoped and useStepper.

One stepper: wrap your tree with <Scoped> and call useStepper() in children.

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

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

Two steppers: a global (e.g. wizard) and a local one (sub-steps). Each has its own Scoped and useStepper.

import {  } from "@stepperize/react";

const  = (
  { : "start", : "Start" },
  { : "middle", : "Middle" },
  { : "end", : "End" }
);

const  = (
  { : "sub1", : "Sub-step 1" },
  { : "sub2", : "Sub-step 2" }
);

Wrap your UI with Scoped

Scoped is the provider: it holds the stepper state. Any component inside it can call the matching useStepper() and get that stepper instance. For multi-scoped, nest the local Scoped inside the global one so inner components can use both.

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

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

function () {
  return (
    <>
      < />
      < />
    </>
  );
}

function () {
  const  = ();
  return <>{/* content per step */}</>;
}

function () {
  const  = ();
  return <>{/* buttons */}</>;
}
import * as React from "react";
import {  } from "@stepperize/react";

const  = (
  { : "start", : "Start" },
  { : "middle", : "Middle" },
  { : "end", : "End" }
);

const  = (
  { : "sub1", : "Sub-step 1" },
  { : "sub2", : "Sub-step 2" }
);

export function () {
  return (
    <.>
      < />
      <.>
        < />
        < />
      </.>
      < />
    </.>
  );
}

function () {
  const  = .();
  return <>Global: {....}</>;
}

function () {
  const  = .();
  return (
    <>
      {!.. && (
        < ="button" ={() => ..()}>Back</>
      )}
      {!.. && (
        < ="button" ={() => ..()}>Next</>
      )}
    </>
  );
}

function () {
  const  = .();
  return <>Local: {....}</>;
}

function () {
  const  = .();
  return (
    <>
      < ="button" ={() => ..()} ={..}>
        Prev sub
      </>
      < ="button" ={() => ..()} ={..}>
        Next sub
      </>
    </>
  );
}

Render content per step (flow.switch / flow.when)

Inside a child component, call useStepper() (or GlobalStepper.useStepper() / LocalStepper.useStepper() in multi-scoped) and use stepper.flow.switch({ ... }) to render different content per step. You can also use flow.when(id, fn) or flow.is(id).

For single scope the pattern is the same in every child. For multi-scoped, global content uses the global stepper's flow.switch; local content uses the local stepper's flow.switch. Each stepper has its own state.

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

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

function () {
  const  = ();
  return (
    <>
      {..({
        : () => <>Step: {.}. Enter your info.</>,
        : () => <>Review your data.</>,
        : () => <>All done!</>,
      })}
    </>
  );
}

Global content uses GlobalStepper.useStepper() and its flow.switch. Inside the local Scoped, local content uses LocalStepper.useStepper() and its flow.switch. You can show the local scope only on a given global step (e.g. "middle") by conditionally rendering the local Scoped when globalStepper.flow.is("middle").

Add navigation (next / prev / reset)

In another child component, call the same useStepper() (or the matching one in multi-scoped) and use stepper.state.isFirst, stepper.state.isLast, and stepper.navigation.next(), prev(), reset().

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

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

function () {
  const  = ();
  return (
    <>
      {.. ? (
        < ="button" ={() => ..()}>
          Reset
        </>
      ) : (
        <>
          {!.. && (
            < ="button" ={() => ..()}>
              Back
            </>
          )}
          < ="button" ={() => ..()}>
            Next
          </>
        </>
      )}
    </>
  );
}

Use GlobalStepper.useStepper() for global nav (Back / Next / Reset) and LocalStepper.useStepper() for local nav (Prev sub / Next sub). Advancing one stepper does not change the other.

Optional: initial step, metadata, or local scope only on one global step

Pass initialStep and/or initialMetadata to Scoped to set the starting step and initial metadata per step.

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

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

function () {
  const  = ();
  return <>{/* content per step */}</>;
}

function () {
  const  = ();
  return <>{/* buttons */}</>;
}

function () {
  return (
    <
      ="review"
      ={{ : { : "Jane" } }}
    >
      < />
      < />
    </>
  );
}

You can show the local Scoped (and local step UI) only when the global stepper is on a specific step. Use a wrapper that calls GlobalStepper.useStepper() and returns the local scope only when stepper.flow.is("middle").

function GlobalStepGate({ children }: { children: React.ReactNode }) {
  const globalStepper = GlobalStepper.useStepper();
  if (!globalStepper.flow.is("middle")) return null;
  return <>{children}</>;
}

// In your tree:
<GlobalStepper.Scoped>
  <GlobalStepContent />
  <GlobalStepGate>
    <LocalStepper.Scoped>
      <LocalStepContent />
      <LocalStepNavigation />
    </LocalStepper.Scoped>
  </GlobalStepGate>
  <GlobalStepNavigation />
</GlobalStepper.Scoped>

Live preview

Current: Info

Enter your info.

Global step: Start

Start content.

Full example

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

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

function () {
  const  = ();
  return (
    <>
      <>Current: {....}</>
      {..({
        : () => <>Enter your info.</>,
        : () => <>Review your data.</>,
        : () => <>All done!</>,
      })}
    </>
  );
}

function () {
  const  = ();
  return (
    <>
      {.. ? (
        < ="button" ={() => ..()}>
          Reset
        </>
      ) : (
        <>
          {!.. && (
            < ="button" ={() => ..()}>
              Back
            </>
          )}
          < ="button" ={() => ..()}>
            Next
          </>
        </>
      )}
    </>
  );
}

export function () {
  return (
    <>
      < />
      < />
    </>
  );
}
import * as React from "react";
import {  } from "@stepperize/react";

const  = (
  { : "start", : "Start" },
  { : "middle", : "Middle" },
  { : "end", : "End" }
);

const  = (
  { : "sub1", : "Sub-step 1" },
  { : "sub2", : "Sub-step 2" }
);

function () {
  const  = .();
  return (
    <>
      <>Global step: {....}</>
      {..({
        : () => <>Start content.</>,
        : () => (
          <.>
            < />
            < />
          </.>
        ),
        : () => <>End content.</>,
      })}
    </>
  );
}

function () {
  const  = .();
  return <>Local step: {....}</>;
}

function () {
  const  = .();
  return (
    <>
      < ="button" ={() => ..()} ={..}>
        Prev
      </>
      < ="button" ={() => ..()} ={..}>
        Next
      </>
    </>
  );
}

function () {
  const  = .();
  return (
    <>
      {!.. && (
        < ="button" ={() => ..()}>Back</>
      )}
      {.. ? (
        < ="button" ={() => ..()}>Reset</>
      ) : (
        < ="button" ={() => ..()}>Next</>
      )}
    </>
  );
}

export function () {
  return (
    <.>
      < />
      < />
    </.>
  );
}

Summary

  • Scoped — Wrap your tree so any descendant can call useStepper() and get the same stepper. For multi-scoped, nest a second Scoped inside the first so inner components can use both hooks.
  • useStepper() — Use in child components for state.current, state.isFirst / state.isLast, flow.switch, navigation.next() / prev() / reset(). In multi-scoped, call the matching hook (GlobalStepper.useStepper() vs LocalStepper.useStepper()).
  • No primitives — Build your own step list, content panels, and buttons; Stepperize only provides state and navigation.

Additional resources

  • Scoped API — Scoped component and useStepper when used with Scoped
  • Hook API — useStepper state, navigation, flow, metadata
Edit on GitHub

Last updated on

On this page