Components

PageSectionWizard

A specialized page header for multi-step wizard flows with editable titles, step indicators, and sidebar integration.

Installation

npm install @marmoui/ui
yarn add @marmoui/ui
pnpm add @marmoui/ui
bun add @marmoui/ui

Usage

import { PageSectionWizard } from '@marmoui/ui';

Examples

Basic Wizard Header

Step Indicators

Wizard header with title and step progress

Create Campaign

<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>

Editable Title

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.

Editable Title

Click the title to rename the wizard

My New Campaign

<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>

With Step Navigation

Clickable Steps

Click completed steps to navigate back

Campaign Builder

<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>

With Stepper Context

When used inside a Stepper component, PageSectionWizard automatically reads the current step and navigation from context. No need to pass currentStep, totalSteps, or onStepClick.

Stepper Integration

Automatic step management via Stepper context

Campaign Builder

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>
);
}

Full-Height Wizard Layout

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.

Wizard content with bottom footer

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>

Full Layout with Sidebar

In a real application, PageSectionWizard sits inside a SidebarProvider alongside SideNavigation. The sidebar toggle button appears when the sidebar is collapsed.

Wizard in Full Layout

PageSectionWizard inside a SidebarProvider with SideNavigation

Create Campaign

Email Content

<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>

Simple Step Counter

If you don't provide steps, the wizard shows a simple "Step X of Y" text.

Step Counter

Minimal step indicator without labels

Setup Wizard

Step 3 of 5
<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>

Custom Steps Content

Use stepsContent to render custom content in the steps area instead of the default step indicators.

Custom Steps Area

Replace step indicators with custom content

Import Contacts

<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>

Best Practices

Use descriptive step labels

Step labels should clearly describe what happens in each step (e.g., "Campaign Details" not "Step 1").

Enable editable titles

When the wizard creates a named entity (campaign, template), let users name it inline rather than in a separate field.

Keep steps to 3-5

More than 5 steps feels overwhelming. Group related steps or use sub-steps.

Keep the footer at the bottom

Use a flex column page body. The wizard content scrolls, and the footer sits after the content with mt-auto or sticky bottom styling.

Enable showSidebarToggle

Always set showSidebarToggle when using inside a SidebarProvider so users can re-open a collapsed sidebar.

Don't use for non-sequential flows

If users can complete sections in any order, use Tabs instead.

Don't mix with PageSection

Use one header type per page. Don't combine PageSection and PageSectionWizard.

Don't wrap steps in Card by default

Wizard steps are page content. Use Card only for bounded widgets or summaries inside a step.

Props

PageSectionWizard

PropTypeDefaultDescription
titlestringWizard title text
onTitleChange(newTitle: string) => voidMakes title editable; called on each keystroke
onTitleChangeEnd(newTitle: string) => voidCalled when editing ends (blur or Enter)
titlePlaceholderstring'Untitled'Placeholder text when title is empty
currentStepnumberCurrent step index (0-based). Auto-detected from Stepper context if omitted.
totalStepsnumberTotal steps. Auto-detected from Stepper context if omitted.
stepsWizardStepItem[]Step definitions for indicators. Uses Stepper context if omitted.
onStepClick(stepIndex: number) => voidCalled when a step indicator is clicked
showSidebarTogglebooleantrueShow sidebar toggle when sidebar is collapsed
stepsContentReactNodeCustom content replacing the default step indicators
classNamestringAdditional className

Accessibility

  • Title input supports keyboard editing (Enter to save, Escape to cancel)
  • Step indicators are keyboard navigable when onStepClick is provided
  • Current step is communicated to screen readers
  • Sidebar toggle button has proper aria-label
  • Editable title has an edit button with tooltip for discoverability