Home/ Cookbook/ Shells & nav/ App shell with sidebar

Build the operator app shell: sidebar, top bar, content

By the end of this recipe you'll have the chrome every Huchu operator app lives inside — a 252 px sidebar with brand mark, search, primary nav, tenant switcher and sign-out; a top bar with breadcrumbs and a bell; a content area that takes the rest. Plus collapse-to-icon-rail, persisted per user, and a Cmd+K shortcut.

Shells 2 primitives · 0 blocks · 1 pattern ~30 min React · @corelithzw/react

Overview

One <AppShell> wraps your whole app. Compose with AppShell.Sidebar, AppShell.Topbar and AppShell.Main — the rest is your pages.

The sidebar app shell is the chrome a cashier, bursar or plant operator sees every working day. The job of this recipe is to make it boring in the right way — the brand mark, the search, the nav, the active state and the sign-out menu always live exactly where the operator's eye expects them, on every screen, in every app you ship. That sameness is what lets a bursar new to the inventory app find "Receipts" in three seconds.

The shell handles three responsibilities: layout (252 px sidebar on desktop, icon-rail at narrower widths, off-canvas drawer on mobile); state (collapsed/expanded persisted to localStorage, route-aware active nav, tenant pinned to the topbar); and shortcuts (Cmd+K for the command palette, Cmd+B to toggle the sidebar). Everything else — page content, breadcrumbs, page header — is rendered by whichever route is active.

Skip this recipe if your app is single-purpose and a one-page UI with a sticky top action bar is enough — adding a sidebar to a single-screen app just adds a column of empty space and a navigation muscle the user will never need.

What you'll build

Three breakpoints, one component. The shell adapts; your pages don't move.

01 Desktop — sidebar expanded (default)
02 Compact — sidebar collapsed to icons
03 Mobile — sidebar off-canvas behind hamburger

You can see this live in

Portal demos that wire this shell in context. Open one to see the sidebar in action on a real workflow.

Required pieces

Everything this recipe pulls from @corelithzw/react. Click any to see its reference page.

@corelithzw/react exports used here: AppShell, AppShell.Sidebar, AppShell.Topbar, AppShell.Main, NavItem, NavGroup, Avatar, DropdownMenu, Input, Kbd, CommandPalette. The AppShell.* dot-namespace is the published-API convention for shells — every shell recipe follows the same shape.

Step-by-step build

01

Lay out the three slots and persist the collapsed state

The shell is grid + slot. AppShell takes three children with named slots; it handles the responsive grid so your pages never need to. Persist the collapsed bit to localStorage with a single hook — the operator stays in their preferred config across sessions, which is the single most-requested shell behaviour.

Code-only step

collapsed → localStorage. Survives sign-out.

Mobile shifts the Sidebar into an off-canvas drawer automatically.

Switch to the Code tab to see the snippet.

Step 1 · Layout + persistence@corelithzw/react
02

Compose the sidebar: brand, search, nav, tenant switcher, sign-out

The sidebar reads top-to-bottom in the same order on every Huchu app: brand → search → primary nav → spacer → tenant + user menu. Use NavItem with the route's match so the active state lights up automatically — no useLocation sprinkled across the tree. The tenant switcher and sign-out live in a single DropdownMenu anchored to the user avatar.

Mukamba
Search⌘ K
Workspace
Overview
Receipts3
Inventory
Staff
Reports
TM
Tendai
Step 2 · Sidebar
03

Compose the topbar: breadcrumb, bell, command palette trigger

The topbar is the one piece of chrome that knows what page you're on. Render a breadcrumb on the left (route-driven), a search-button on the right that opens the same command palette as Cmd+K, and a bell that shows unread notifications. Keep it under 56 px tall — anything more eats content the operator came here to see.

Park Centre / Receipts
Search & jump
3
Step 3 · Topbar
04

Wire the keyboard shortcuts: Cmd+K, Cmd+B, Esc

Three shortcuts cover 95% of operator nav: Cmd+K opens the command palette, Cmd+B toggles the sidebar, Esc closes whichever overlay is open. Bind them at the shell level with one useEffect; never re-bind in pages or you'll get drift the day someone forgets to unmount a listener.

⌘ K open palette · ⌘ B toggle sidebar · Esc close
All bindings live in one effect at shell-level. No drift.
Step 4 · Shortcuts

Final composition

The whole Shell component, assembled. Sidebar slots, topbar slots, palette, shortcuts, persistence. Drop your route output into <Shell> and every page inherits the chrome.

Park Centre / Overview
Search & jump

Park Centre · today

Three tills open, one cashier on lunch.

Revenue
$ 2,816
Sales
147
Avg
$ 19.16
Shell.tsx@corelithzw/react

Variations

Three common forks. Each is a small diff from the final composition — the slots stay the same shape.

Multi-site owner

Owner of three sites. Topbar carries a site picker; sidebar nav groups by site. The site they're focused on is part of the chrome, not a setting.

<AppShell.Topbar.Pickers>
  <SitePicker
    value={siteId}
    options={sites}
    onChange={setSite}
  />
</AppShell.Topbar.Pickers>

Nested settings shell

Inside Settings, the content column splits into a section rail + content. Reuse AppShell.Main as the host; render a SettingsShell inside.

<AppShell.Main>
  <SettingsShell sections={SETTINGS}>
    {/* settings page */}
  </SettingsShell>
</AppShell.Main>

Focus mode

A workflow (close shift, post payroll). Hide the sidebar, swap the topbar for a brand + step header. Same shell, two props.

<AppShell mode="focus">
  <AppShell.Topbar.Step
    title="Close shift S-2841"
    current={2} total={4}
  />
  <AppShell.Main>{workflow}</AppShell.Main>
</AppShell>

Accessibility

What this recipe takes care of for you. Each item is something a screen-reader or keyboard user will notice.

  • Landmarks for free. AppShell.Sidebar renders as <nav aria-label="Primary">; AppShell.Main as <main>; AppShell.Topbar as <header>. Screen-reader users can jump straight to nav or content with rotor.
  • Active route is announced. NavItem renders aria-current="page" on the matching route, so SR users hear "Overview, current page" instead of just "Overview".
  • Collapse is keyboard-toggleable. Cmd/Ctrl+B toggles the sidebar; collapse-button has a real aria-pressed + aria-controls.
  • Search input has a kbd hint and focuses the palette. Focusing the input opens the same palette as Cmd+K — keyboard-only users don't have to remember the shortcut.
  • Tenant menu is a real menu. DropdownMenu uses role="menu"; arrow keys move between items, Enter selects, Esc closes and returns focus to the trigger.
  • Bell announces the count. aria-label="Notifications (3 unread)", not just a bell icon — SR users hear the number.
  • Mobile drawer traps focus. When the off-canvas sidebar is open, Tab cycles inside it; Esc closes it and returns focus to the hamburger trigger.