Page and route#
/components/app-shell, also reachable standalone at /shell-demo (a plain re-export outside the gallery layout, apps/playground/src/app/shell-demo/page.tsx:1-3, so the shell can fill the whole viewport). Rendered by AppShellGallery in apps/playground/src/app/components/app-shell/gallery.tsx.
What the reader sees#
One tab strip switching between seven real shell shapes: an interactive playground shell, walmart's actual shell, speedway's actual shell, a collapsed rail, a topbar with every slot filled, a sidebar with counts and a colored group, and a sidebar with footer identity. Below the live shell, a two-column key/value grid restates exactly which props that variant passed, so the picture and the prop list can be checked against each other without opening the source.
Layer 1: shell#
This page IS the shell's own showcase, so "Layer 1" here is the primitive itself, AppShell (packages/ui/src/app-shell), rather than something wrapping the page. The page that hosts the gallery sits inside the playground's own navigation, the standard sidebar-plus-topbar chrome every other playground page shares; the /shell-demo route deliberately opts out of that outer chrome (shell-demo/page.tsx:1-2) so the AppShell instances rendered inside the gallery are not nested inside a second, real shell.
Layer 2: composition#
Every playground page shares one frame: PageFrame (apps/playground/src/app/page-frame.tsx:6-15), a centered max-w-6xl column, and ShowcaseTitle (apps/playground/src/app/showcase-title.tsx:24-40), the icon/title/subtitle/actions header resolved from the route registry. AppShellGallery follows that shape exactly: PageFrame (app-shell/gallery.tsx:127) wraps ShowcaseTitle (:128, with a conditional ThemeToggleButton action only on the standalone /shell-demo route, since that route has no navbar toggle of its own to fall back on), one ExampleCard holding the seven-way tab strip (:130-164), and a SlotMapCard (:166-173).
Layer 3: primitives#
| Primitive | Props as called | file:line | Contract doc |
|---|---|---|---|
PageFrame | the shared max-w-6xl content column | app-shell/gallery.tsx:127 | shared across every playground page, not itself a kit export |
ShowcaseTitle | actions conditional on pathname === "/shell-demo" | app-shell/gallery.tsx:124, 128 | apps/playground/src/app/showcase-title.tsx |
ExampleCard | title="The shell, seven ways", subtitle, toolbar (the "Open full layout" button), source/sourceTitle from the per-variant example file | app-shell/gallery.tsx:130-145 | apps/playground/src/app/example-card.tsx |
Button | the toolbar's "Open full layout in a new tab" link, variant="text" with a trailing Right arrow | app-shell/gallery.tsx:134-143 | packages/ui/docs/button.md |
Tabs | items={VARIANT_ITEMS}, variant="enclosed", size="sm" | app-shell/gallery.tsx:148 | packages/ui/docs/tabs.md |
AppShell (×7, inside the example files) | one full shell instance per tab: interactive-shell, walmart-shell, speedway-shell (a re-creation, not the live app), collapsed-rail, topbar-search-actions, sidebar-counts-colored-group, sidebar-footer-identity | app-shell/gallery.tsx:150-160, each in its own examples/*.tsx | packages/ui/docs/app-shell.md |
SlotMapCard | the four AppShellClassNames slot names | app-shell/gallery.tsx:166-173 | apps/playground/src/app/slot-map-card.tsx |
Layer 4: patterns#
- Sidebar anatomy (
/patterns/sidebar-anatomy): every one of the seven tabbed shells is, at its core, this pattern (grouped rail, known-only counts, identity footer) with a different combination ofAppShellprops filled in; the gallery is effectively seven worked instances of the one pattern page, side by side. - Not a pattern page but worth naming:
app-shell.md's own Travels-with section (app-shell.md:72) calls the auth-bootSkeletonfill forsidebarTop/sidebarFooter"the shell-level half of" the boot-skeleton ideaspinner.mddocuments; this gallery does not demonstrate that boot state, since every tab renders the shell already loaded.
Layer 5: canon rules in force#
docs/design-language/08-navigation-and-shell.md§A3 (:21): active state is explicit, never inferred by prefix. Thewalmart-shellandspeedway-shellexample files reproduce each app's ownactivecomputation rather than a generic placeholder, perapp-shell.md:43's Sanctioned-combinations entry.packages/ui/docs/app-shell.md:17:AppShellis not assumed to be the shell for every app on the kit; thespeedway-shelltab exists precisely to show the shape speedway built instead, inside the gallery that documents the primitive speedway does not use.docs/design-language/09-page-composition.md§A1: theinteractive-shelltab bounds itself to a fixedh-[520px]box (app-shell/gallery.tsx:151) rather than the page's own viewport, so a live, scrollable shell demo can sit inside a reading page without hijacking the whole screen.
Lapses#
Fixed 2026-08-17: the toolbar's navigation button now takes the kit's prescribed shape. app-shell/gallery.tsx:134-143 renders the "Open full layout in a new tab" link as variant="text" color="primary" Icon="Right" iconRight inside the ExampleCard's toolbar slot, the navigation shape button.md:57's Sanctioned entry names and every landing section and the docs home already use. It used to be variant="outline" color="primary" with a hand-typed "↗" appended to the label, which is not the literal banned "bare outline + neutral" of button.md:69 but skirts the same entry's instruction for what a card-toolbar link should be.
A coverage gap, not a defect. The interactive shell tab bounds its demo to h-[520px] and never exercises the AppShell boot state (Skeleton in sidebarTop/sidebarFooter, per app-shell.md's Travels-with section, :72). Walmart's own real boot render is the only place this state is demonstrated today; the gallery could add an eighth tab for it, but nothing in the canon requires one.
See also#
docs/breakdowns/06-kit-button-gallery.md, the primitive whose Banned section this doc's Lapses entry cites.packages/ui/docs/app-shell.md, the contract doc every one of this page's seven tabs demonstrates a slice of./patterns/sidebar-anatomy, the pattern page these seven shapes are all variations on.