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:

Wizard.Fullscreen Portal overlay
Wizard.Grid Optional Decorative wireframe
Wizard Step state, context, and navigation
Wizard.Steps Filters and indexes active steps
Wizard.Sidebar Optional Responsive step indicator
Wizard.Step Individual step with card-stack animation
Wizard.Page Card surface with title, description, body, and footer

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();
PropertyTypeDescription
back() => void

Go back to the previous step. No-op on the first step.

close() => void

Programmatically close the wizard. Calls Wizard.Fullscreen’s onClose. No-op if no onClose is set.

complete() => void

Fires the onComplete callback. Not gated by isLastStep — the consumer decides when to call it.

goToStep(key: string) => void

Navigate to a step by its stepKey. No-op if the key is not mounted.

isChangingStepboolean

Whether an onBeforeStepChange guard is pending. Use to show a loading state on navigation buttons.

isFirstStepboolean

Whether the wizard is on its first step.

isLastStepboolean

Whether the wizard is on its last registered step.

itemsWizardStepItem[]

Array of registered step metadata (key, label, hideFromSidebar).

previousStepNavigation”enabled” | “disabled”

Whether implicit previous-step affordances are enabled. Explicit back() calls still work when disabled.

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 goToStep, next, or back.

stepnumber

Current active step index (0-based).

stepKeystring | undefined

Key of the current active step (Wizard.Step’s stepKey).

totalStepsnumber

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>
  );
}
ReturnTypeDescription
gridProps{ activeCardHeight: number; isTransitioning: boolean }

The active card’s height and transition state. Spread onto Wizard.Grid.

onActiveStepElementChange(el: HTMLDivElement | null) => void

Pass to Wizard so the grid can measure the active step.

API Reference

Wizard

Manages step state, navigation, and the card-stack animation. Render it inside Wizard.Fullscreen.

PropTypeDefaultDescription
childrenReactNode-Wizard content — `Wizard.Steps`, `Wizard.Step`, etc.
classNamestring-Additional CSS classes merged via `cn()`.
defaultStepnumber | string0Initial step (index or `stepKey`) for uncontrolled mode. Ignored when `step` is provided.
labelsWizardLabels-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.
stepnumber | 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.

PropTypeDefaultDescription
classNamestring-Override content wrapper classes.
openboolean-Controls visibility. Returns null when false.
onClose() => void-Called on Escape or close button click.
containerPortalContainerdocument.bodyPortal target element. Overrides KumoPortalProvider context.
labelsWizardFullscreenLabels-Labels for i18n of aria-labels. close: aria-label for the close button (default: Close).
headerReactNode-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.

PropTypeDefaultDescription
classNamestring-Additional CSS classes.
activeCardHeight*number-Measured card height for border animation. Use useWizardGrid().
isTransitioning*boolean-Whether a step transition is in progress. Use useWizardGrid().
titlestring-Left-side title, visible at @5xl/wizard (64rem).
topOffsetnumber147Distance from viewport top to the grid.

Wizard.Sidebar

Responsive step indicator. Compose it inside Wizard when you want the sidebar treatment.

PropTypeDefaultDescription
classNamestring-Additional CSS classes.

Wizard.Steps

Wraps the steps. Drops any step with when={false} and indexes the rest.

PropTypeDefaultDescription
classNamestring-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.

PropTypeDefaultDescription
stepKey*string-Unique identifier for this step.
labelstring-Label shown in the sidebar navigation.
hideFromSidebarbooleanfalseHides this step from the sidebar list.
whenbooleantrueWhether 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.

PropTypeDefaultDescription
classNamestring-Additional CSS classes for the card.
titlestring-Card heading text.
descriptionstring | ReactNode-Description shown below the title.
footerReactNode-Footer content (e.g., navigation buttons). Rendered outside the primary card surface.
maxHeightstring"~100vh − 400px"Max height of the card. Override with any CSS value (e.g. "600px", "80dvh").