Wizard
The full-page wizard page template — a sticky header band above a capped content column.
Usage
import {
Wizard,
WizardHeader,
WizardSubtitle,
WizardBody,
} from '@acronis-platform/ui-react';Wizard is the page template for a full-page, multi-step flow — Create
dashboard, Create protection plan, and the like. It renders a sticky header band
(WizardHeader) above a content column capped at 1024px (WizardBody).
It is a composition, not a new primitive: everything you see inside it
already exists in the kit and Wizard reimplements none of it. The trail is a
Breadcrumb. The title row is
PageHeader's — PageHeaderRow, PageHeaderTitle
and PageHeaderActions, reused rather than duplicated as WizardTitle /
WizardActions. The step indicator is a Stepper with
StepperItem children. The step's fields live in a
Section. Wizard's own contribution is the skeleton:
the band's surface, divider, padding, and inter-row gap, plus the content
column's cap.
PageHeader's root is deliberately not reused for the band — it carries
role="banner", and a wizard's header band is not the page banner. Composing the
row parts alone keeps the landmark structure honest inside an
AppShell that already has one.
What Wizard deliberately does not do
Wizard holds no step index, fires no navigation events, and renders no
buttons. Which of Cancel / Back / Next / Submit appears on a given step, and
what each does, is a per-flow product decision owned by the consuming UI block —
PageHeaderActions is a slot you fill. This is the same boundary PageHeader
draws around its own actions slot. Likewise the Stepper is optional: a two- or
three-step flow may drop it, and Wizard neither requires one nor substitutes a
fallback.
That boundary is about the components, not the package: if you'd rather not
hand-maintain one StepperItem block per step, opt into the
useWizard hook, which holds the step index
for you. It is separate and optional — it renders nothing and still leaves button
visibility to you.
The band's chrome comes from --ui-background-surface-secondary,
--ui-border-on-surface-divider, --ui-gap-16 and --ui-gap-12; the subtitle
uses the muted --ui-text-on-surface-secondary. There is no --ui-wizard-*
token tier and none is needed — every themed surface inside belongs to a composed
component that owns its own.
Examples
<Wizard>
<WizardHeader>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="#">Dashboards</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Create dashboard</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<PageHeaderRow>
<PageHeaderTitle>Create dashboard</PageHeaderTitle>
<PageHeaderActions>
<Button variant="secondary">Cancel</Button>
<Button variant="secondary">Back</Button>
<Button>Next</Button>
</PageHeaderActions>
</PageHeaderRow>
<WizardSubtitle>
Name the dashboard, pick its widgets, and choose who can see it.
</WizardSubtitle>
<Stepper
currentStep={2}
totalSteps={3}
current="Choose widgets"
next="Set permissions"
>
<StepperItem
variant="completed"
label="Name the dashboard"
avatar={
<Avatar color="green" className="[box-shadow:none]">
<AvatarFallback>1</AvatarFallback>
</Avatar>
}
/>
<StepperItem
variant="current"
label="Choose widgets"
avatar={
<Avatar
color="blue"
className="[box-shadow:none] text-[var(--ui-stepper-item-current-label-color)]"
>
<AvatarFallback>2</AvatarFallback>
</Avatar>
}
/>
<StepperItem
variant="future"
label="Set permissions"
avatar={
<Avatar
color="gray"
className="[box-shadow:none] text-[var(--ui-stepper-item-future-label-color)]"
>
<AvatarFallback>3</AvatarFallback>
</Avatar>
}
/>
</Stepper>
</WizardHeader>
<WizardBody>
<Section>
<SectionHeader
title="Choose widgets"
description="Widgets you add here appear on the dashboard in the order you pick them."
hasDescription
/>
<SectionContent>{/* step fields */}</SectionContent>
</Section>
</WizardBody>
</Wizard>The example above writes each StepperItem out by hand to show the full markup.
In a real flow you'd derive them — see the next section.
Driving the steps with useWizard
useWizard is the opt-in headless companion to these presentational components.
Declare the steps once and it owns the step index, returning the Stepper summary
props, a derived steps array carrying each step's variant and Avatar
colour/classes, and the navigation callbacks you wire to Back/Next.
import { useWizard, type WizardStep } from '@acronis-platform/ui-react';
const STEPS: WizardStep[] = [
{ id: 'name', label: 'Name the dashboard' },
{ id: 'widgets', label: 'Choose widgets' },
{ id: 'permissions', label: 'Set permissions' },
];
function CreateDashboard() {
const wizard = useWizard({ steps: STEPS });
return (
<Wizard>
<WizardHeader>
<PageHeaderRow>
<PageHeaderTitle>Create dashboard</PageHeaderTitle>
<PageHeaderActions>
<Button variant="secondary">Cancel</Button>
{!wizard.isFirstStep && !wizard.isLastStep && (
<Button variant="secondary" onClick={wizard.goToPreviousStep}>
Back
</Button>
)}
{wizard.isLastStep ? (
<Button>Create dashboard</Button>
) : (
<Button onClick={wizard.goToNextStep}>Next</Button>
)}
</PageHeaderActions>
</PageHeaderRow>
<Stepper
currentStep={wizard.currentStepNumber}
totalSteps={wizard.stepCount}
current={wizard.currentStepLabel}
// undefined on the last step, so the "Next: …" line is left out
next={wizard.nextStepLabel}
>
{wizard.steps.map((step) => (
<StepperItem
key={step.id}
variant={step.variant}
label={step.label}
avatar={
<Avatar
color={step.avatarColor}
className={step.avatarClassName}
>
<AvatarFallback>{step.stepNumber}</AvatarFallback>
</Avatar>
}
/>
))}
</Stepper>
</WizardHeader>
<WizardBody>{/* the current step's Section */}</WizardBody>
</Wizard>
);
}Which buttons a step shows is still yours to decide — the hook only tells you
where you are (isFirstStep / isLastStep); the pairing above follows the
design brief ([Cancel] [CTA] on the first and last steps, [Cancel] [Back] [Next] in the middle). The hook also does no validation, branching, skipping
or persistence: gate goToNextStep behind your own form validation if a step
must be valid before advancing.
initialStep is a seed, not a controlled input — pass a zero-based index
(truncated toward zero and clamped into range) or a step id; later changes to
it are ignored. steps itself may change between renders: if it shrinks past
the active step, the active index clamps to the last remaining step. If you
already own your step state, keep driving Stepper / StepperItem by hand: the
hook is additive and nothing requires it.
Short flow — no stepper
A wizard of two or three steps may omit the step indicator entirely; the subtitle then carries the context.
<Wizard>
<WizardHeader>
<PageHeaderRow>
<PageHeaderTitle>Create dashboard</PageHeaderTitle>
<PageHeaderActions>
<Button variant="secondary">Cancel</Button>
<Button>Create dashboard</Button>
</PageHeaderActions>
</PageHeaderRow>
<WizardSubtitle>Name it and choose who can see it.</WizardSubtitle>
</WizardHeader>
<WizardBody>
<Section>{/* … */}</Section>
</WizardBody>
</Wizard>A wider step
There is no width variant — Figma defines one content width. A step that needs
more room (a data table, say) overrides the cap through className, the same way
Dialog takes a per-instance width.
<WizardBody className="max-w-[1280px]">
<Section>{/* a data-table step */}</Section>
</WizardBody>API Reference
Wizard is markup-only: it exposes no component-specific props, and in
particular no step / currentStep / onNext / onBack — see
What Wizard deliberately does not do.
The step indicator's own currentStep / totalSteps props belong to
Stepper.
useWizard
useWizard<TStep extends WizardStep>(options): UseWizardResult<TStep>| Option | Type | Description |
|---|---|---|
steps | TStep[] | The flow's steps in order, each { id, label } plus any extra fields of your own. Must not be empty. |
initialStep | number | string | Where the flow starts — a zero-based index (truncated, then clamped) or a step id. NaN or an unknown id falls back to the first step. A seed; later changes are ignored. |
| Returns | Type | Description |
|---|---|---|
steps | WizardStepState<TStep>[] | Your steps, each with index, stepNumber, variant, avatarColor, avatarClassName. |
currentIndex | number | Zero-based index of the active step; always a valid index into the current steps. |
currentStepNumber | number | One-based active step, for Stepper's currentStep. |
stepCount | number | Total steps, for Stepper's totalSteps. |
currentStepLabel | string | The active step's label, for Stepper's current. |
nextStepLabel | string | undefined | The following step's label; undefined on the last step. |
isFirstStep | boolean | Whether the active step is the first. |
isLastStep | boolean | Whether the active step is the last. |
goToNextStep | () => void | Advance one step; a no-op on the last step (never wraps). |
goToPreviousStep | () => void | Go back one step; a no-op on the first step (never wraps). |
goToStep | (target: number | string) => void | Jump to a zero-based index (truncated toward zero, then clamped) or a step id. NaN or an unknown id is a no-op. |
Each derived step's variant is completed before the active index, current
at it, and future after it; avatarColor / avatarClassName are the matching
StepperItem avatar treatment, ready to spread onto
an Avatar. An empty steps array throws.
Parts
| Export | Element | Purpose |
|---|---|---|
Wizard | div | The template root — a full-height flex column. |
WizardHeader | div | The sticky header band: breadcrumb, title row, optional subtitle, optional stepper. Owns the surface, divider, padding and inter-row gap. |
WizardSubtitle | p | Optional muted supporting line under the title row (the same treatment as PageHeaderDescription; there is no separate Subtitle). |
WizardBody | div | The step's content column, capped at 1024px. Always wraps a Section. |
All parts accept their native element attributes plus className.