AppShell
since v12.3.0AppShell — dashboard frame: persistent sidebar (lg+) or Drawer-collapsed (< lg),
topbar, and main content region. Active-nav styling belongs in the consumer's
sidebar — the active indicator is the documented 2px bg-brand left-edge stripe
(DESIGN.md Brand Voltage Rule: the sidebar's one brand moment), label stays ink.
Install
Add the package and import the component.
pnpm add @hey-mike/tungstenpnpm add @hey-mike/tungstenimport { AppShell } from '@hey-mike/tungsten';import { AppShell } from '@hey-mike/tungsten';Preview
Same fixtures used by the visual-regression suite.
Usage
apps/docs/app/snapshots/app-shell/page.tsx
'use client';
import { AppShell } from '@hey-mike/tungsten';
import { HeroSpecimen } from '../_components/HeroSpecimen';
export default function AppShellSnapshot() {
return (
<div
data-testid="snapshot-root"
className="h-screen w-[1024px]"
>
<AppShell
sidebar={
<nav aria-label="Primary navigation" className="flex flex-col gap-1 p-4">
<a href="/dashboard" className="text-ink-1 bg-surface-hover rounded-md px-3 py-2 text-sm font-medium">
Dashboard
</a>
<a href="/users" className="text-ink-2 hover:bg-surface-hover rounded-md px-3 py-2 text-sm">
Users
</a>
<a href="/settings" className="text-ink-2 hover:bg-surface-hover rounded-md px-3 py-2 text-sm">
Settings
</a>
</nav>
}
topbar={
<div className="text-ink-2 flex items-center gap-2 text-sm">
<span>Dashboard</span>
</div>
}
>
<h2 className="text-ink-1 text-lg font-semibold">Overview</h2>
<p className="text-ink-3 mt-1 text-sm">Welcome to your dashboard.</p>
</AppShell>
{/* Gallery thumbnail (`?hero`): the real AppShell is full-screen (1024px) and
collapses to blank at thumbnail scale, so show a compact static diagram of
the shell anatomy — sidebar rail + topbar + content. Hidden in the detail
view; revealed (and the real AppShell hidden) by the hero CSS. */}
<div data-hero-specimen className="hidden">
<HeroSpecimen>
<div className="border-stroke bg-surface flex h-[120px] w-[208px] overflow-hidden rounded-lg border">
{/* sidebar rail */}
<div className="bg-surface-2 border-stroke flex w-12 flex-col gap-1.5 border-r p-2">
<div className="bg-brand-overlay-15 h-2 rounded-sm" />
<div className="bg-ink-3/25 h-2 rounded-sm" />
<div className="bg-ink-3/25 h-2 rounded-sm" />
</div>
{/* main column */}
<div className="flex flex-1 flex-col">
<div className="border-stroke flex h-6 items-center border-b px-2">
<div className="bg-ink-3/30 h-1.5 w-10 rounded-sm" />
</div>
<div className="flex flex-col gap-1.5 p-2.5">
<div className="bg-ink-3/25 h-2 w-2/3 rounded-sm" />
<div className="bg-ink-3/15 h-2 w-1/2 rounded-sm" />
<div className="bg-ink-3/15 h-2 w-3/5 rounded-sm" />
</div>
</div>
</div>
</HeroSpecimen>
</div>
</div>
);
}
'use client';
import { AppShell } from '@hey-mike/tungsten';
import { HeroSpecimen } from '../_components/HeroSpecimen';
export default function AppShellSnapshot() {
return (
<div
data-testid="snapshot-root"
className="h-screen w-[1024px]"
>
<AppShell
sidebar={
<nav aria-label="Primary navigation" className="flex flex-col gap-1 p-4">
<a href="/dashboard" className="text-ink-1 bg-surface-hover rounded-md px-3 py-2 text-sm font-medium">
Dashboard
</a>
<a href="/users" className="text-ink-2 hover:bg-surface-hover rounded-md px-3 py-2 text-sm">
Users
</a>
<a href="/settings" className="text-ink-2 hover:bg-surface-hover rounded-md px-3 py-2 text-sm">
Settings
</a>
</nav>
}
topbar={
<div className="text-ink-2 flex items-center gap-2 text-sm">
<span>Dashboard</span>
</div>
}
>
<h2 className="text-ink-1 text-lg font-semibold">Overview</h2>
<p className="text-ink-3 mt-1 text-sm">Welcome to your dashboard.</p>
</AppShell>
{/* Gallery thumbnail (`?hero`): the real AppShell is full-screen (1024px) and
collapses to blank at thumbnail scale, so show a compact static diagram of
the shell anatomy — sidebar rail + topbar + content. Hidden in the detail
view; revealed (and the real AppShell hidden) by the hero CSS. */}
<div data-hero-specimen className="hidden">
<HeroSpecimen>
<div className="border-stroke bg-surface flex h-[120px] w-[208px] overflow-hidden rounded-lg border">
{/* sidebar rail */}
<div className="bg-surface-2 border-stroke flex w-12 flex-col gap-1.5 border-r p-2">
<div className="bg-brand-overlay-15 h-2 rounded-sm" />
<div className="bg-ink-3/25 h-2 rounded-sm" />
<div className="bg-ink-3/25 h-2 rounded-sm" />
</div>
{/* main column */}
<div className="flex flex-1 flex-col">
<div className="border-stroke flex h-6 items-center border-b px-2">
<div className="bg-ink-3/30 h-1.5 w-10 rounded-sm" />
</div>
<div className="flex flex-col gap-1.5 p-2.5">
<div className="bg-ink-3/25 h-2 w-2/3 rounded-sm" />
<div className="bg-ink-3/15 h-2 w-1/2 rounded-sm" />
<div className="bg-ink-3/15 h-2 w-3/5 rounded-sm" />
</div>
</div>
</div>
</HeroSpecimen>
</div>
</div>
);
}
Props
Surface specific to <AppShell />.
| Prop | Type | Default | Description |
|---|---|---|---|
| sidebar* | ReactNode | — | Primary navigation. Rendered fixed on wide (lg+), inside Drawer on narrow. |
| topbar | ReactNode | — | Top bar content (breadcrumb, search, account). |
| className | string | — | — |