Component reference
AppShell
Application regions with a header, rails and footer.
Preview
Live previewOptional six-digit hex. Clear to restore the preset. Check contrast with your brand color.
Changes apply to this preview only.
Width adapts to the available space
"use client";
import {
AppShell,
Badge,
Button,
Sidebar,
SidebarHeader,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarItem,
SidebarFooter,
Grid,
Card,
CardContent,
Stat,
CardHeader,
CardTitle,
CardDescription
} from "@pimolu/react";
import { ReactNode } from "react";
type DemoNavIconName = "overview" | "projects" | "people" | "analytics" | "billing" | "settings";
const paths: Record<DemoNavIconName, ReactNode> = {
overview: <>
<rect x="3" y="3" width="7" height="7" rx="1" />
<rect
x="14"
y="3"
width="7"
height="7"
rx="1"
/>
<rect
x="3"
y="14"
width="7"
height="7"
rx="1"
/>
<rect
x="14"
y="14"
width="7"
height="7"
rx="1"
/>
</>,
projects: <>
<rect x="3" y="6" width="18" height="15" rx="2" />
<path
d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"
/>
</>,
people: <>
<circle cx="9" cy="8" r="3" />
<path
d="M3.5 20v-2a5.5 5.5 0 0 1 11 0v2M17 5a3 3 0 0 1 0 6m2.5 9v-2a5.5 5.5 0 0 0-3-5"
/>
</>,
analytics: <>
<path d="M4 20V12m5 8V7m5 13v-5m5 5V4" strokeLinecap="round" />
<path
d="M2 21h20"
/>
</>,
billing: <>
<rect x="2.5" y="5" width="19" height="14" rx="2" />
<path
d="M2.5 9.5h19M6 15h4"
/>
</>,
settings: <>
<circle cx="12" cy="12" r="3" />
<path
d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9 7 7m10 10 2.1 2.1M19.1 4.9 17 7M7 17l-2.1 2.1"
strokeLinecap="round"
/>
</>,
};
function DemoNavIcon({ name }: {
name: DemoNavIconName;
}) {
return (
<svg
viewBox="0 0 24 24"
width="18"
height="18"
fill="none"
stroke="currentColor"
strokeWidth="1.7"
aria-hidden="true"
>
{paths[name]}
</svg>
);
}
function ShellHeader() {
return (
<div
className="flex flex-wrap items-center gap-3 min-w-0 justify-between"
style={{
flexWrap: "wrap"
}}
>
<div className="flex flex-wrap items-center gap-3 min-w-0">
<span
className="grid place-items-center size-9 rounded-md bg-primary text-[var(--pimolu-primary-foreground)]"
>P</span>
<div>
<strong className="block">Acme Console</strong>
<span className="text-xs text-muted">Operations workspace</span>
</div>
</div>
<div className="flex flex-wrap items-center gap-3 min-w-0">
<Badge variant="success">Live</Badge>
<Button size="sm" appearance="outline">Search</Button>
<Button size="sm">New project</Button>
</div>
</div>
);
}
function WorkspaceSidebar({ compact = false }: {
compact?: boolean;
}) {
return (
<Sidebar collapsible>
<SidebarHeader>
<span
className="grid place-items-center size-9 rounded-md bg-primary text-[var(--pimolu-primary-foreground)]"
>A</span>
<div>
<strong className="block">Acme</strong>
<span className="text-xs text-muted">
{compact ? "Team" : "Design system"}
</span>
</div>
</SidebarHeader>
<SidebarContent aria-label="Workspace">
<SidebarGroup>
<SidebarGroupLabel>Workspace</SidebarGroupLabel>
<SidebarItem
href="/components/app-shell"
active
icon={<DemoNavIcon
name="overview"
/>}
>
Overview
</SidebarItem>
<SidebarItem
href="/components/data-table"
icon={<DemoNavIcon
name="projects"
/>}
>
Projects
</SidebarItem>
<SidebarItem href="/components/stat" icon={<DemoNavIcon name="analytics" />}>Analytics</SidebarItem>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupLabel>Manage</SidebarGroupLabel>
<SidebarItem
href="/components/form-field"
icon={<DemoNavIcon name="billing" />}
>
Billing
</SidebarItem>
<SidebarItem href="/components/dialog" icon={<DemoNavIcon name="settings" />}>Settings</SidebarItem>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<span className="text-sm text-muted leading-relaxed">Acme Inc. · Pro plan</span>
</SidebarFooter>
</Sidebar>
);
}
const activity = [
{
name: "Ada Lovelace",
action: "shipped the Q3 report",
time: "8 min",
},
{
name: "Noah Dubois",
action: "invited three teammates",
time: "26 min",
},
{
name: "Maya Chen",
action: "connected a data source",
time: "1 h",
},
];
function WorkspaceAside() {
return (
<div className="grid gap-5 min-w-0">
<div>
<h2 className="mb-2 text-lg">Activity</h2>
<p className="text-sm text-muted leading-relaxed">What changed recently.</p>
</div>
{
activity.map((item) => (<div key={item.name} className="grid gap-5 min-w-0">
<strong>{item.name}</strong>
<span className="text-sm text-muted leading-relaxed">
{item.action} · {item.time} ago
</span>
</div>))
}
<Button size="sm" appearance="outline">View all activity</Button>
</div>
);
}
function WorkspaceContent({ compact = false, }: {
compact?: boolean;
}) {
return (
<div className="grid gap-5 min-w-0">
<div className="flex flex-wrap items-center gap-3 min-w-0 justify-between">
<div>
<h2 className="mb-2 text-lg">Workspace overview</h2>
<p className="max-w-prose leading-relaxed">
{
compact
? "The same shell without a sidebar or right rail."
: (
"A full-width header and footer around a navigation rail, the " +
"main region and a contextual aside."
)
}
</p>
</div>
<Badge variant="primary">Q3</Badge>
</div>
<Grid columns="auto-fit" minItemWidth={170} gap={3}>
<Card>
<CardContent>
<Stat
label="Monthly revenue"
value="$48,200"
delta="+12.4%"
trend="up"
description="Compared with September"
/>
</CardContent>
</Card>
<Card>
<CardContent>
<Stat
label="Active users"
value="12,840"
delta="+8.2%"
trend="up"
description="7,310 weekly active"
/>
</CardContent>
</Card>
<Card>
<CardContent>
<Stat
label="Open tasks"
value="24"
delta="+3"
trend="up"
description="Across six projects"
/>
</CardContent>
</Card>
</Grid>
<Card>
<CardHeader>
<CardTitle>Release readiness</CardTitle>
<CardDescription>
Main content is a real landmark; any component can live inside
it.
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-5 min-w-0">
<div className="flex flex-wrap items-center gap-3 min-w-0 justify-between">
<span>Design tokens</span>
<Badge variant="success">Ready</Badge>
</div>
<div className="flex flex-wrap items-center gap-3 min-w-0 justify-between">
<span>Documentation</span>
<Badge variant="warning">Review</Badge>
</div>
<div className="flex flex-wrap items-center gap-3 min-w-0 justify-between">
<span>Native screens</span>
<Badge variant="neutral">Draft</Badge>
</div>
</div>
</CardContent>
</Card>
</div>
);
}
function ShellFooter() {
return (
<div className="flex flex-wrap items-center gap-3 min-w-0 justify-between">
<span>Acme Inc. · Internal workspace</span>
<span>All systems operational</span>
</div>
);
}
export function DemoExample() {
return (
<AppShell
header={<ShellHeader />}
sidebar={<WorkspaceSidebar />}
aside={<WorkspaceAside />}
footer={<ShellFooter />}
stickyHeader
sidebarWidth="15rem"
asideWidth="16rem"
sidebarLabel="Workspace navigation"
asideLabel="Recent activity"
style={{
minHeight: 640
}}
>
<WorkspaceContent />
</AppShell>
);
}More examples
Different configurations and compositions using the real Pimolu API. Each preview runs independently and follows the site appearance.
Editor with a status footer
A header and footer around a document, without navigation rails.
Main content only
Both rails are optional. The shell collapses to a single column while keeping the header, main and footer landmarks.
Custom rail widths
sidebarWidth and asideWidth accept any CSS length or a number of pixels. The shell sets the matching custom properties.
Usage
Choose AppShell for generic application structure and DashboardLayout for its opinionated workspace composition.
API reference
Generated from the public TypeScript declarations. Native HTML attributes are inherited where the component extends them; this reference includes Pimolu props and selected common native attributes.
PimoluAppShellProps
Type source| Name | Type | Default | Description |
|---|---|---|---|
headerRequired | ReactNode | — | Full-width application header. Required: every shell has chrome. |
sidebar | ReactNode | — | Optional navigation rail, usually a Sidebar. |
childrenRequired | ReactNode | — | Main application content. |
footer | ReactNode | — | Optional full-width footer. |
aside | ReactNode | — | Optional complementary rail on the inline end. |
stickyHeader | boolean | undefined | false | Keep the header visible while the content scrolls. |
sidebarWidth | Property.Width<string | number> | undefined | 16rem | Width of the sidebar. Defaults to 16rem. |
asideWidth | Property.Width<string | number> | undefined | 18rem | Width of the aside. Defaults to 18rem. |
sidebarLabel | string | undefined | "Sidebar" | Accessible name of the sidebar landmark. Defaults to "Sidebar". |
asideLabel | string | undefined | "Aside" | Accessible name of the aside landmark. Defaults to "Aside". |
defaultValue | string | number | readonly string[] | undefined | — | Standard React/HTML attribute. |
className | string | undefined | — | Standard React/HTML attribute. |
style | CSSProperties | undefined | — | Standard React/HTML attribute. |
aria-label | string | undefined | — | Defines a string value that labels the current element. |
onChange | ChangeEventHandler<HTMLDivElement, Element> | undefined | — | Standard React/HTML attribute. |
onClick | MouseEventHandler<HTMLDivElement> | undefined | — | Standard React/HTML attribute. |
headerRequiredReactNodeNo default extracted.
Full-width application header. Required: every shell has chrome.
sidebarReactNodeNo default extracted.
Optional navigation rail, usually a Sidebar.
childrenRequiredReactNodeNo default extracted.
Main application content.
footerReactNodeNo default extracted.
Optional full-width footer.
asideReactNodeNo default extracted.
Optional complementary rail on the inline end.
stickyHeaderboolean | undefinedDefault: false
Keep the header visible while the content scrolls.
sidebarWidthProperty.Width<string | number> | undefinedDefault: 16rem
Width of the sidebar. Defaults to 16rem.
asideWidthProperty.Width<string | number> | undefinedDefault: 18rem
Width of the aside. Defaults to 18rem.
sidebarLabelstring | undefinedDefault: "Sidebar"
Accessible name of the sidebar landmark. Defaults to "Sidebar".
asideLabelstring | undefinedDefault: "Aside"
Accessible name of the aside landmark. Defaults to "Aside".
defaultValuestring | number | readonly string[] | undefinedNo default extracted.
Standard React/HTML attribute.
classNamestring | undefinedNo default extracted.
Standard React/HTML attribute.
styleCSSProperties | undefinedNo default extracted.
Standard React/HTML attribute.
aria-labelstring | undefinedNo default extracted.
Defines a string value that labels the current element.
onChangeChangeEventHandler<HTMLDivElement, Element> | undefinedNo default extracted.
Standard React/HTML attribute.
onClickMouseEventHandler<HTMLDivElement> | undefinedNo default extracted.
Standard React/HTML attribute.
Accessibility
- Name navigation/aside regions and verify one main landmark in the standalone document.