references/shell.md
The admin shell
Read after SKILL.md. The shell is the single template every route renders inside, so it is where the app stops looking like a collection of pages.
Anatomy
┌─ TopBar 48px ──────────────── logo · breadcrumb ····· ⌘K · bell · avatar ─┐
├──────────┬───────────────────────────────────────────────┬───────────────┤
│ Sidebar │ content area (the only scrolling region) │ Inspector │
│ 240px │ │ 320px │
│ collapse │ │ (optional, │
│ → 56px │ │ tabbed) │
├──────────┴───────────────────────────────────────────────┴───────────────┤
└─ StatusBar 28px ── branch · sync · selection · "Press ? for shortcuts" ───┘
All four dimensions are tokens (--topbar-height, --sidebar-width, --sidebar-collapsed, --inspector-width, --statusbar-height), so the shell never hard-codes a pixel that a component also needs to know.
body { overflow: hidden }. The content area is the only scroll container. Skipping this gives you a page scrollbar and a panel scrollbar, which is the single most recognisable sign of an admin UI that was assembled rather than designed.
Template signature
interface AdminShellProps {
breadcrumb: { label: string; href?: string }[];
/** Optional override for the bottom status bar. */
statusBar?: ReactNode;
/** Page-specific palette items appended to the global ones. */
paletteItems?: PaletteItem[];
children: ReactNode;
}
A page passes a breadcrumb and its content. Everything else - nav, palette, shortcuts, toasts, auth gating - is the shell's problem. Keep this surface small; every prop added here is a prop every page has to think about.
Auth belongs in the shell too, wrapping the whole tree:
export function AdminShell(props: AdminShellProps) {
return (
<RequireAdmin>
<AdminShellInner {...props} />
</RequireAdmin>
);
}
That way no page can forget to check, and there is exactly one place to audit.
Sidebar
Sectioned nav, not a flat list - group by workflow (Overview / Repository / Operations / Admin). Collapses to a 56px icon rail. Items carry an optional count badge for pending work, fed from one hook rather than each item fetching its own.
Active item: brand-orange 2px left border plus bg-brand-soft. Not a filled orange row - see rule 3 in SKILL.md.
Command palette
⌘K. This is the feature that makes the whole thing feel like a tool rather than a website, and it is worth building early.
- Results grouped by kind (Files / Folders / Commands), not one flat list
- Fully keyboard driven: arrows, enter, escape
- Selections actually navigate - a palette that only filters is theatre
- The shell provides global commands; each page appends its own via
paletteItems
Global commands read as "Go to Vehicles", "New auction" - verb-first, matching what someone would type.
Keyboard map
Document every binding in a ? overlay, grouped. If a shortcut is not in the overlay, it does not exist as far as users are concerned.
| Key | Action |
|---|---|
⌘K | Command palette |
? | Shortcut overlay |
1 / 2 | List view / grid view |
Esc | Close palette, overlay, or context menu |
Chord shortcuts (G then F) are a small state machine and a nice touch, but document them before you implement them - the overlay is the contract.
Interaction inventory
What a complete shell handles, roughly in order of value:
- Toasts - bottom-right stack, coloured left border by kind, auto-dismiss.
Every destructive or async action fires one.
- Context menu on right-click - Open / Rename / Move / Duplicate /
Copy path / Download / Delete.
- Status bar - current branch, sync state, selection count, latency, and a
standing Press ? for shortcuts hint.
- Drag and drop, in two flavours that people conflate:
- OS files into the window - full-area dashed-orange overlay with
"Drop to upload to /\<folder\>".
- Row onto a folder row - target folder gets a dashed orange outline,
source rows go half-transparent.
Wire the visual layer first and have drops fire toasts. That is genuinely useful for design review, but be honest in the code that the backend is not connected - a drop that silently does nothing reads as a bug.
Deliberately skipped
Worth knowing so you do not burn a day on them:
- Drag-out to desktop (HTML5 drag-out to download) - browser-quirky and
rarely worth speculative work.
- Multi-drag chip ("4 items" near the cursor) - possible with custom drag
images, purely nice-to-have.
- Skeleton rows and empty-state illustrations - add when there is real async
loading to cover, not before.