Acronis UIKit
Components

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>
OptionTypeDescription
stepsTStep[]The flow's steps in order, each { id, label } plus any extra fields of your own. Must not be empty.
initialStepnumber | stringWhere 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.
ReturnsTypeDescription
stepsWizardStepState<TStep>[]Your steps, each with index, stepNumber, variant, avatarColor, avatarClassName.
currentIndexnumberZero-based index of the active step; always a valid index into the current steps.
currentStepNumbernumberOne-based active step, for Stepper's currentStep.
stepCountnumberTotal steps, for Stepper's totalSteps.
currentStepLabelstringThe active step's label, for Stepper's current.
nextStepLabelstring | undefinedThe following step's label; undefined on the last step.
isFirstStepbooleanWhether the active step is the first.
isLastStepbooleanWhether the active step is the last.
goToNextStep() => voidAdvance one step; a no-op on the last step (never wraps).
goToPreviousStep() => voidGo back one step; a no-op on the first step (never wraps).
goToStep(target: number | string) => voidJump 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

ExportElementPurpose
WizarddivThe template root — a full-height flex column.
WizardHeaderdivThe sticky header band: breadcrumb, title row, optional subtitle, optional stepper. Owns the surface, divider, padding and inter-row gap.
WizardSubtitlepOptional muted supporting line under the title row (the same treatment as PageHeaderDescription; there is no separate Subtitle).
WizardBodydivThe step's content column, capped at 1024px. Always wraps a Section.

All parts accept their native element attributes plus className.

Edit on GitHub

On this page