Layout

PageSection

A layout component for page headers with breadcrumb navigation, titles, action buttons, and wizard step indicators

Installation

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

Usage

Import the PageSection components from the package:

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

Examples

Basic Page Title

Simple Title

Display a standalone page title

Dashboard

<div className="w-full border border-border rounded-lg overflow-hidden">
			<PageSection pageTitle="Dashboard" />
		</div>

With Breadcrumbs

Breadcrumbs are not a mirror of sidebar navigation. Use them only when the page has real hierarchy or parent context, such as analytics for a specific module (campaign, course, product), a single contact page, a file-manager folder path, or a folder that groups related modules. Use pageTitle for ordinary sidebar destinations.

Breadcrumb Navigation

Show hierarchical navigation with breadcrumbs

<div className="w-full border border-border rounded-lg overflow-hidden">
			<PageSection
				breadcrumbs={[
					{ label: 'Home', href: '#' },
					{ label: 'Projects', href: '#' },
					{ label: 'Website Redesign' },
				]}
			/>
		</div>

With Actions

Action Buttons

Add primary and secondary action buttons

User Management

<div className="w-full border border-border rounded-lg overflow-hidden">
			<PageSection
				pageTitle="User Management"
				primaryAction={{ label: 'Add User', onClick: () => {} }}
				secondaryActions={[
					{ label: 'Import', onClick: () => {} },
					{ label: 'Export', onClick: () => {} },
				]}
				otherActions={[
					{ label: 'Settings', onClick: () => {} },
					{ label: 'Help', onClick: () => {} },
				]}
			/>
		</div>

Layout variants (top bar)

PageSection has five variants that match the Figma Page Section component. Use AppLayout to pick the layout + variant pair automatically, or set variant manually.

VariantFigmaTypical layout
defaultDefaultsidebar-collapsible — brand + search + utilities
workspaceLayout 2sidebar-hidden — logo-in-toggle + search; nav row under header
globalLayout 3top-nav — horizontal nav with mega menus
compactLayout 4dual-sidebar — title/breadcrumbs + actions only (default)
profileProfile HeaderUser profile pages

Breadcrumbs always render on a single line (including the last crumb) across every variant.

See the App layouts pattern for the full selection flow.

Default — brand + search

Figma Default variant for collapsible sidebar apps

<SidebarProvider mainNavItems={[]} config={{ defaultSecondaryOpen: true }}>
			<div className="w-full overflow-hidden rounded-lg border border-border">
				<PageSection
					variant="default"
					breadcrumbs={[{ label: 'Projects' }]}
					searchPlaceholder="Search"
				/>
			</div>
		</SidebarProvider>

Global — horizontal nav

Figma Layout 3 for top-nav apps

<div className="w-full overflow-hidden rounded-lg border border-border">
			<PageSection
				variant="global"
				globalNavItems={[
					{ label: 'Dashboards', hasMenu: true, isActive: true },
					{ label: 'Components', hasMenu: true },
					{ label: 'Templates', hasMenu: true },
				]}
			/>
		</div>

Compact — title only

Figma Layout 4 for dual-sidebar apps

Projects

<SidebarProvider mainNavItems={[]} config={{ defaultSecondaryOpen: false }}>
			<div className="w-full overflow-hidden rounded-lg border border-border">
				<PageSection variant="compact" pageTitle="Projects" />
			</div>
		</SidebarProvider>

Actions on Breadcrumb

Add contextual actions to the last breadcrumb item

<div className="w-full border border-border rounded-lg overflow-hidden">
			<PageSection
				breadcrumbs={[
					{ label: 'Campaigns', href: '#' },
					{
						label: 'Summer Sale Campaign',
						actions: [
							{ label: 'Rename', icon: <MdOutlineEdit />, onClick: () => {} },
							{ label: 'Duplicate', icon: <MdOutlineCopyAll />, onClick: () => {} },
							{ label: 'Archive', icon: <MdOutlineArchive />, onClick: () => {} },
							{ label: 'Delete', icon: <MdOutlineDelete />, isDelete: true, onClick: () => {} },
						],
					},
				]}
				primaryAction={{ label: 'Save Changes', onClick: () => {} }}
			/>
		</div>

Wizard Variant

PageSectionWizard is designed for multi-step form flows with editable titles and step indicators.

Basic Wizard

Wizard Header

Step indicator for multi-step forms

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

Editable Wizard Title

Allow users to edit the wizard title inline

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>

Simple Step Counter

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

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 breadcrumbs for deep navigation

Use breadcrumbs for real hierarchy: module analytics detail pages, single contact pages, file-manager folders/files, and folders that group related modules.

Use pageTitle for regular sidebar pages

Sidebar navigation and breadcrumbs solve different problems. A top-level sidebar destination usually needs pageTitle, not breadcrumbs.

Limit visible actions

Show at most 1-2 primary actions. Use the overflow menu for additional actions.

Make primary action prominent

The primary action should be the most important action on the page.

Use wizard for multi-step flows

Use PageSectionWizard when guiding users through a sequential process.

Don't use long breadcrumb labels

Keep breadcrumb labels concise. Truncate if necessary.

Don't mix page header styles

Use one consistent page header pattern throughout your application.

Common Mistakes

  • The page's main call-to-action belongs in primaryAction, never in the page body. Do not render a toolbar row under the header with the primary button (e.g. "Import CSV", "Create contact"). Pass it as primaryAction={{ label: 'Import CSV', onClick: … }} so it renders inside the header, aligned with the title.
  • PageSection is a header, do not pass children into it. Use it self-closing as <PageSection pageTitle="…" primaryAction={…} /> and place page content in a sibling element beneath it. PageSection is not a wrapper.
  • Do not generate breadcrumbs from the active sidebar item. Breadcrumbs are for hierarchy and parent context, not for repeating the navigation menu.
  • Do not wrap the entire page body inside <PageSection>…</PageSection>. That pattern ignores the component's real prop contract (pageTitle, breadcrumbs, primaryAction, secondaryActions, otherActions) and will not render correctly.
  • Pass actions as prop objects, not as rendered JSX. primaryAction={{ label: 'Save', onClick: () => {} }} — not primaryAction={<Button>Save</Button>}.
  • Do not use the deprecated legacy title / subtitle / children props. Those belong to an older, private PageSection component that is no longer part of @marmoui/ui. Use pageTitle and breadcrumbs instead.

Props

PageSection

PropTypeDefaultDescription
variant'default' | 'workspace' | 'global' | 'compact' | 'profile''compact'Top bar layout — see App layouts
pageTitlestring-Standalone page title (alternative to breadcrumbs)
breadcrumbsPageSectionBreadcrumbItem[]-Array of breadcrumb items
primaryActionPageSectionActionItem-Primary CTA button configuration
secondaryActionsPageSectionSecondaryActionItem[]-Secondary action buttons
otherActionsPageSectionActionItem[]-Overflow menu actions
showSidebarTogglebooleantrueShow sidebar toggle when sidebar is collapsed
globalNavItemsPageSectionGlobalNavItem[]-Horizontal nav links (variant="global"); items with hasMenu open a mega menu populated from menu groups
logoInTogglebooleanfalseRender the brand logo inside the sidebar toggle button (Layout 2); hides the standalone brand
searchPlaceholderstring'Search'Search input placeholder (default / workspace)
brandLabelstring'Marmo'Brand text beside logo
profilePageSectionProfile-Profile header content (variant="profile")
classNamestring-Additional className

PageSectionWizard

PropTypeDefaultDescription
titlestring-Wizard title
onTitleChange(newTitle: string) => void-Makes title editable when provided
onTitleChangeEnd(newTitle: string) => void-Called when title editing ends
titlePlaceholderstring'Untitled'Placeholder when title is empty
currentStepnumber-Current step index (0-based)
totalStepsnumber-Total number of steps
stepsWizardStepItem[]-Array of step definitions
onStepClick(stepIndex: number) => void-Called when a step is clicked
showSidebarTogglebooleantrueShow sidebar toggle when sidebar is collapsed
stepsContentReactNode-Custom content for steps area

Accessibility

Navigation Landmarks

PageSection uses proper semantic HTML with navigation landmarks for breadcrumbs and heading elements for titles.

  • Breadcrumbs use <nav> with proper ARIA labels
  • Action buttons are keyboard accessible
  • Editable titles support keyboard interaction (Enter to submit, Escape to cancel)
  • Step indicators communicate current progress to screen readers