AppShell
The frame of an app: a header with the brand, the context and the account, a navigation beside the
page on a wide screen and folded into a menu on a narrow one, and the main landmark, reached
first by a skip link.
import { AppShell } from '@zitdevs/ui';Examples
Section titled “Examples”The demo is server HTML with no React on the page, which runs one script,
enhanceAppShellMenus(document) from @zitdevs/ui/enhance, exactly as a site without React would.
Narrow the window below 1024px, open the menu, and press Escape: it closes and focus goes back to
the toggle. Press Tab once from the top of the page to meet the skip link.
<AppShell brand={<Logo />} context={<Tag label="Development" />} account={<Button label="Ada Lovelace" variant="ghost" size="sm" />} nav={<SideNav id="app-nav" label="Main" groups={groups} />}> <PageHeader title="People" /> {/* the page */}</AppShell>In Next.js it works from a Server Component in app/layout.tsx; only the menu’s small client part
ships to the browser.
| Prop | Type | Default | Notes |
|---|---|---|---|
brand |
ReactNode |
required | The product’s mark and name, at the start of the header. |
nav |
ReactNode |
required | The navigation, a SideNav. |
context |
ReactNode |
— | Where the reader is working, on every page: an environment, an organization. |
account |
ReactNode |
— | Who is signed in, at the end of the header. |
children |
ReactNode |
— | The page. Start it with a PageHeader. |
mainId |
string |
'main' |
The id of the main landmark, which the skip link names. |
labels |
Partial<AppShellLabels> |
English | { skipToContent, menu }. es.appShell from labels/es. |
ref |
Ref<HTMLDivElement> |
— | The shell’s root. |
className |
string |
— | Placement; the shell is a viewport tall by default. |
| anything else | <div> attributes |
— |
Exported types: AppShellProps, AppShellLabels, and the label sets appShellLabels. From
@zitdevs/ui/enhance: enhanceAppShellMenus.
Accessibility
Section titled “Accessibility”- Landmarks: a
<header>(the banner), the navigation, and<main>. APageHeaderinside the page is a header of the page, not a second banner. - The skip link is the first thing a keyboard reaches and shows when it has focus. It names the
<main>, which takes focus (tabindex="-1"), so the next Tab starts in the page. - The menu’s toggle is a
<summary>, so Enter and Space operate it. The enhancer mirrors the open state asaria-expanded, closes on Escape and gives focus back to the toggle, and closes when a link in it is followed. - From the
lgwidth up the navigation is a sidebar: the enhancer holds it open and the toggle goes away. Escape and links leave it alone there. - The environment shows in the header on every page, as text.
Without JavaScript
Section titled “Without JavaScript”The server renders the menu closed, in a <details>, and a <details> opens and closes with no
script: the toggle is reachable at every width, the navigation behind it. Without the enhancer
the toggle stays at desktop width too, so the navigation is never out of reach. What the enhancer
adds is the Escape key with focus restored, aria-expanded, closing on a followed link, and the
open sidebar at desktop width:
- React: nothing to do. The menu is a small client part that runs the enhancer once it has hydrated.
- No React: call
enhanceAppShellMenus(document)once. It finds everydetails[data-app-shell-menu]and enhances each once.
Theming
Section titled “Theming”surface for the page and surface-raised for the header and the menu; border-default for the
lines; ink, muted and body for text; the focus ring. A product re-themes the shell by
overriding the roles.