import { Button, Wizard, useWizardGrid } from "@cloudflare/kumo";
import { WizardDemoHeader } from "./wizard-demo-header";
import { useCreateWorkerDemo } from "./use-create-worker-demo";
export function WizardFullDemo() {
const { gridProps, onActiveStepElementChange } = useWizardGrid();
// Demo-only state plumbing, not part of the Wizard API.
const demo = useCreateWorkerDemo();
return (
<>
<Button onClick={demo.handleOpen}>Open Wizard</Button>
<Wizard.Fullscreen
open={demo.open}
onClose={demo.handleClose}
className="relative"
header={<WizardDemoHeader />}
>
<Wizard.Grid {...gridProps} title="Create a Worker">
<Wizard
step={demo.stepKey}
onStepChange={demo.handleStepChange}
onActiveStepElementChange={onActiveStepElementChange}
previousStepNavigation={demo.isDeploying ? "disabled" : "enabled"}
>
<Wizard.Sidebar />
<Wizard.Steps>
<Wizard.Step stepKey="method" label="Select a method">
<Wizard.Page
title="Ship something new"
description="Choose how you want to start."
>
{/* ... */}
</Wizard.Page>
</Wizard.Step>
<Wizard.Step
stepKey="configure"
label="Configure"
when={demo.isDeployPath || demo.isTemplatePath}
>
<Wizard.Page
title="Configure your Worker"
description="Set the project name, build command, and deploy command."
>
{/* ... */}
</Wizard.Page>
</Wizard.Step>
</Wizard.Steps>
</Wizard>
</Wizard.Grid>
</Wizard.Fullscreen>
</>
);
} Installation
Barrel
import { Wizard, useWizard, useWizardGrid } from "@cloudflare/kumo"; Granular
import { Wizard, useWizard, useWizardGrid } from "@cloudflare/kumo/components/wizard"; Anatomy
The Wizard is a compound component. Compose only the pieces you need:
Usage
Compose the parts inside Wizard.Fullscreen. useWizardGrid() links Wizard and Wizard.Grid so the wireframe tracks the active step.
import { Button, Wizard, useWizard, useWizardGrid } from "@cloudflare/kumo";
function CreateWorker({ open, onClose }) {
const { gridProps, onActiveStepElementChange } = useWizardGrid();
return (
<Wizard.Fullscreen open={open} onClose={onClose}>
<Wizard.Grid title="Create a Worker" {...gridProps}>
<Wizard onActiveStepElementChange={onActiveStepElementChange}>
<Wizard.Sidebar />
<Wizard.Steps>
<Wizard.Step stepKey="method" label="Select a method">
<Wizard.Page
title="Ship something new"
description="Choose how you want to start."
footer={<Footer />}
>
{/* ... */}
</Wizard.Page>
</Wizard.Step>
{/* ...more steps */}
</Wizard.Steps>
</Wizard>
</Wizard.Grid>
</Wizard.Fullscreen>
);
}
function Footer() {
const { back, next, isFirstStep } = useWizard();
return (
<>
<Button variant="ghost" onClick={back} disabled={isFirstStep}>
Back
</Button>
<Button onClick={next}>Continue</Button>
</>
);
} Hooks
useWizard()
Access wizard state from any descendant component. Must be used within a
Wizard.
const { step, stepKey, goToStep, next, back, close, isLastStep, isFirstStep } = useWizard(); | Property | Type | Description |
|---|---|---|
| back | () => void | Go back to the previous step. No-op on the first step. |
| close | () => void | Programmatically close the wizard. Calls |
| complete | () => void | Fires the |
| goToStep | (key: string) => void | Navigate to a step by its |
| isChangingStep | boolean | Whether an |
| isFirstStep | boolean | Whether the wizard is on its first step. |
| isLastStep | boolean | Whether the wizard is on its last registered step. |
| items | WizardStepItem[] | Array of registered step metadata (key, label, hideFromSidebar). |
| previousStepNavigation | ”enabled” | “disabled” | Whether implicit previous-step affordances are enabled. Explicit
|
| next | () => void | Advance to the next mounted step. No-op on the last step. |
| onStepChange | (step: number) => void | Navigate to a step by numeric index. Prefer |
| step | number | Current active step index (0-based). |
| stepKey | string | undefined | Key of the current active step ( |
| totalSteps | number | Total number of registered steps. |
useWizardGrid()
Bridges Wizard and Wizard.Grid by tracking the active card’s height and
managing transition state. Returns gridProps to spread onto Wizard.Grid.
import { Wizard, useWizardGrid } from "@cloudflare/kumo";
function CreateWizard({ step, setStep }) {
const { gridProps, onActiveStepElementChange } = useWizardGrid();
return (
<Wizard.Grid {...gridProps}>
<Wizard
step={step}
onStepChange={setStep}
onActiveStepElementChange={onActiveStepElementChange}
>
<Wizard.Steps>{/* ... */}</Wizard.Steps>
</Wizard>
</Wizard.Grid>
);
}| Return | Type | Description |
|---|---|---|
| gridProps | { activeCardHeight: number; isTransitioning: boolean } | The active card’s height and transition state. Spread onto
|
| onActiveStepElementChange | (el: HTMLDivElement | null) => void | Pass to |
API Reference
Wizard
Manages step state, navigation, and the card-stack animation. Render it inside
Wizard.Fullscreen.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | - | Wizard content — `Wizard.Steps`, `Wizard.Step`, etc. |
| className | string | - | Additional CSS classes merged via `cn()`. |
| defaultStep | number | string | 0 | Initial step (index or `stepKey`) for uncontrolled mode. Ignored when `step` is provided. |
| labels | WizardLabels | - | Labels for internationalization of aria-labels. All labels have English defaults. |
| previousStepNavigation | "enabled" | "disabled" | "enabled" | Controls implicit previous-step affordances. Explicit back() calls still work when disabled. |
| step | number | string | - | Current active step (0-based index or stepKey string). Controlled mode. |
| onStepChange | (index: number, key: string) => void | - | Callback when step changes. Receives both the numeric index and the step key. |
| onBeforeStepChange | (from: number, to: number) => boolean | Promise<boolean> | - | Async validation guard fired before every step transition. Return false to block. |
| onComplete | () => void | - | Fired when the user advances past the last step (e.g. clicks Done). |
| onActiveStepElementChange | (element: HTMLDivElement | null) => void | - | Callback invoked when the active step DOM element changes. Used by useWizardGrid(). |
Wizard.Fullscreen
Fullscreen modal container with a floating close button, Escape-to-close,
scroll lock, and role="dialog". Content fills the entire viewport. The
width prop controls the card’s max-width. Pass header to render arbitrary
top chrome — include Wizard.CloseButton inside the header where you want the
close affordance. Header height is measured for layout.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Override content wrapper classes. |
| open | boolean | - | Controls visibility. Returns null when false. |
| onClose | () => void | - | Called on Escape or close button click. |
| container | PortalContainer | document.body | Portal target element. Overrides KumoPortalProvider context. |
| labels | WizardFullscreenLabels | - | Labels for i18n of aria-labels. close: aria-label for the close button (default: Close). |
| header | ReactNode | - | Optional header above wizard content. Use Wizard.CloseButton inside for close affordance. Height is measured for layout. |
| width | "narrow" | "wide" | "narrow" | Card width preset. Applies with or without Wizard.Grid. narrow = 38rem, wide = 48rem. |
Wizard.Grid
Decorative animated wireframe with dashed borders, corner crosses, and an optional left-side title. The bottom border and crosses animate with the active card’s measured height.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes. |
| activeCardHeight* | number | - | Measured card height for border animation. Use useWizardGrid(). |
| isTransitioning* | boolean | - | Whether a step transition is in progress. Use useWizardGrid(). |
| title | string | - | Left-side title, visible at @5xl/wizard (64rem). |
| topOffset | number | 147 | Distance from viewport top to the grid. |
Wizard.Sidebar
Responsive step indicator. Compose it inside Wizard when you want the
sidebar treatment.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes. |
Wizard.Steps
Wraps the steps. Drops any step with when={false} and indexes the rest.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes for the step container. |
Wizard.Step
Defines a single step with a stepKey and label. Its children animate in
the card stack.
| Prop | Type | Default | Description |
|---|---|---|---|
| stepKey* | string | - | Unique identifier for this step. |
| label | string | - | Label shown in the sidebar navigation. |
| hideFromSidebar | boolean | false | Hides this step from the sidebar list. |
| when | boolean | true | Whether this step is active in the current flow. When false, the step is excluded from rendering, indexing, sidebar, and navigation. |
Wizard.Page
Card layout inside a wizard step. Wraps content in a LayerCard with optional
title, description, scrollable body, and footer.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes for the card. |
| title | string | - | Card heading text. |
| description | string | ReactNode | - | Description shown below the title. |
| footer | ReactNode | - | Footer content (e.g., navigation buttons). Rendered outside the primary card surface. |
| maxHeight | string | "~100vh − 400px" | Max height of the card. Override with any CSS value (e.g. "600px", "80dvh"). |