npm install @marmoui/uiyarn add @marmoui/uipnpm add @marmoui/uibun add @marmoui/uiimport { PageSectionWizard } from '@marmoui/ui';Wizard header with title and step progress
<SidebarProvider mainNavItems={[]} config={{ defaultSecondaryOpen: false }}>
<div className="w-full border border-border rounded-lg overflow-hidden">
<PageSectionWizard
title="Create Campaign"
currentStep={1}
totalSteps={4}
steps={[
{ id: 'details', label: 'Details' },
{ id: 'content', label: 'Content' },
{ id: 'audience', label: 'Audience' },
{ id: 'review', label: 'Review' },
]}
/>
</div>
</SidebarProvider>When onTitleChange is provided, the title becomes inline-editable. Click the title or the pencil icon to edit. Press Enter to save or Escape to cancel.
Click the title to rename the wizard
<SidebarProvider mainNavItems={[]} config={{ defaultSecondaryOpen: false }}>
<div className="w-full border border-border rounded-lg overflow-hidden">
<PageSectionWizard
title={title}
onTitleChange={setTitle}
onTitleChangeEnd={(newTitle) => console.log('Saved:', newTitle)}
titlePlaceholder="Untitled Campaign"
currentStep={0}
totalSteps={3}
steps={[
{ id: 'details', label: 'Details' },
{ id: 'content', label: 'Content' },
{ id: 'review', label: 'Review' },
]}
/>
</div>
</SidebarProvider>Click completed steps to navigate back
<SidebarProvider mainNavItems={[]} config={{ defaultSecondaryOpen: false }}>
<div className="w-full border border-border rounded-lg overflow-hidden">
<PageSectionWizard
title="Campaign Builder"
currentStep={currentStep}
totalSteps={steps.length}
steps={steps}
onStepClick={setCurrentStep}
/>
</div>
</SidebarProvider>When used inside a Stepper component, PageSectionWizard automatically reads the current step and navigation from context. No need to pass currentStep, totalSteps, or onStepClick.
Automatic step management via Stepper context
import { Stepper, StepperContent, PageSectionWizard } from '@marmoui/ui';
function CampaignWizard() {
const [activeStep, setActiveStep] = useState('details');
return (
<Stepper value={activeStep} onValueChange={setActiveStep}>
<PageSectionWizard
title="Campaign Builder"
onTitleChange={(title) => updateCampaignName(title)}
steps={[
{ id: 'details', label: 'Campaign Details' },
{ id: 'content', label: 'Email Content' },
{ id: 'audience', label: 'Select Audience' },
{ id: 'review', label: 'Review & Send' },
]}
/>
<StepperContent value="details">
<CampaignDetailsForm />
</StepperContent>
</Stepper>
);
}Wizard content should fill the available page height. Keep the step content in a
scrollable flex child and place the footer after it with mt-auto (or sticky
bottom styling for long forms). Do not wrap the entire wizard step in Card by
default.
A wizard page structure that keeps navigation actions pinned to the bottom.
<div className="flex min-h-screen flex-col bg-bg">
<PageSectionWizard
title="Create campaign"
steps={[
{ id: 'setup', label: 'Setup' },
{ id: 'audience', label: 'Audience' },
{ id: 'review', label: 'Review' },
]}
currentStep={0}
totalSteps={3}
/>
<main className="flex min-h-0 flex-1 flex-col">
<div className="min-h-0 flex-1 overflow-auto px-space-2xl py-space-xl">
<form className="mx-auto w-full max-w-3xl space-y-4">
{/* Step fields go here. Use Card only for widgets inside the step. */}
</form>
</div>
<footer className="mt-auto flex items-center justify-between border-t border-border-secondary bg-panel px-space-2xl py-space-md">
<Button variant="secondary">Back</Button>
<div className="flex gap-2">
<Button variant="secondary">Save draft</Button>
<Button>Continue</Button>
</div>
</footer>
</main>
</div>In a real application, PageSectionWizard sits inside a SidebarProvider alongside SideNavigation. The sidebar toggle button appears when the sidebar is collapsed.
PageSectionWizard inside a SidebarProvider with SideNavigation
<SidebarProvider mainNavItems={[]} config={{ defaultSecondaryOpen: true }}>
<div className="flex h-[400px] w-full border border-secondary overflow-hidden">
<SidebarPanel>
<SidebarPanelHeader>
<SidebarPanelTitle>Navigation</SidebarPanelTitle>
</SidebarPanelHeader>
<SidebarPanelContent>
<SidebarItem
label="Dashboard"
iconOutlined={MdOutlineDashboard}
iconFilled={MdDashboard}
/>
<SidebarItem
label="Campaigns"
iconOutlined={MdOutlineCampaign}
iconFilled={MdCampaign}
isActive
>
<SidebarSubItem label="Templates" />
<SidebarSubItem label="All Campaigns" isActive />
</SidebarItem>
</SidebarPanelContent>
</SidebarPanel>
<div className="flex-1 flex flex-col overflow-hidden">
<PageSectionWizard
title="Create Campaign"
showSidebarToggle
currentStep={1}
totalSteps={4}
steps={[
{ id: 'details', label: 'Details' },
{ id: 'content', label: 'Content' },
{ id: 'audience', label: 'Audience' },
{ id: 'review', label: 'Review' },
]}
/>
<main className="flex-1 overflow-y-auto px-space-2xl">
<div className="max-w-2xl">
<h3 className="font-medium mb-4">Email Content</h3>
<div className="space-y-4">
<div className="h-8 bg-bg rounded w-full" />
<div className="h-32 bg-bg rounded w-full" />
</div>
</div>
</main>
</div>
</div>
</SidebarProvider>If you don't provide steps, the wizard shows a simple "Step X of Y" text.
Minimal step indicator without labels
<SidebarProvider mainNavItems={[]} config={{ defaultSecondaryOpen: false }}>
<div className="w-full border border-border rounded-lg overflow-hidden">
<PageSectionWizard
title="Setup Wizard"
currentStep={2}
totalSteps={5}
/>
</div>
</SidebarProvider>Use stepsContent to render custom content in the steps area instead of the default step indicators.
Replace step indicators with custom content
<SidebarProvider mainNavItems={[]} config={{ defaultSecondaryOpen: false }}>
<div className="w-full border border-border rounded-lg overflow-hidden">
<PageSectionWizard
title="Import Contacts"
stepsContent={
<div className="flex items-center gap-2">
<Button variant="secondary" size="sm">Cancel</Button>
<Button variant="primary" size="sm">Import 234 Contacts</Button>
</div>
}
/>
</div>
</SidebarProvider>Step labels should clearly describe what happens in each step (e.g., "Campaign Details" not "Step 1").
When the wizard creates a named entity (campaign, template), let users name it inline rather than in a separate field.
More than 5 steps feels overwhelming. Group related steps or use sub-steps.
Use a flex column page body. The wizard content scrolls, and the footer sits after the content with mt-auto or sticky bottom styling.
Always set showSidebarToggle when using inside a SidebarProvider so users can re-open a collapsed sidebar.
If users can complete sections in any order, use Tabs instead.
Use one header type per page. Don't combine PageSection and PageSectionWizard.
Wizard steps are page content. Use Card only for bounded widgets or summaries inside a step.
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Wizard title text |
onTitleChange | (newTitle: string) => void | — | Makes title editable; called on each keystroke |
onTitleChangeEnd | (newTitle: string) => void | — | Called when editing ends (blur or Enter) |
titlePlaceholder | string | 'Untitled' | Placeholder text when title is empty |
currentStep | number | — | Current step index (0-based). Auto-detected from Stepper context if omitted. |
totalSteps | number | — | Total steps. Auto-detected from Stepper context if omitted. |
steps | WizardStepItem[] | — | Step definitions for indicators. Uses Stepper context if omitted. |
onStepClick | (stepIndex: number) => void | — | Called when a step indicator is clicked |
showSidebarToggle | boolean | true | Show sidebar toggle when sidebar is collapsed |
stepsContent | ReactNode | — | Custom content replacing the default step indicators |
className | string | — | Additional className |
onStepClick is providedaria-label