Axon 911 Frontend DocsAxon 911 Frontend Docs
Axon 911 Frontend Docs
    Loading navigation…
U
User
Switch to light mode
  1. Blocks
  2. App Shell
  3. Usage

App Shell

Usage

Import @prepared911/ui/blocks/app-shell and its styles.css, plus the styles.css of each component the block composes (Box, Link, VisuallyHidden, Navbar, Sidebar, Banner). AppShell is the page shell of an application and the root wrapper of every page. It places a Navbar, an optional Sidebar, one Banner, the page, and space for the Dock. It holds layout and focus only: it fetches nothing, stores nothing and knows no router.
Regions
  • navbar is required. The bar is fixed at the block-start edge whatever the page does. Its header is the page's banner landmark, so the shell places it outside main and outside any sectioning element.
  • sidebar is optional. It is fixed at the inline-start edge below the bar and the banner, and it scrolls its own content. Without it main takes the width.
  • banner is optional and takes one element: a notice directly under the bar across the full width of the shell, above the sidebar as well. Notices do not stack.
  • main is required. It scrolls on its own; the bar, the sidebar and the Dock's space never move.
  • dock is optional. It reserves space under main, centered, so the Dock never covers the page. Without it no space is reserved.
The shell fills the dynamic viewport (fit="viewport", the default), so the document does not scroll. Render it as the only thing on the page and clear the body margin (the opt-in reset.css does). In a bounded parent, such as a preview or a panel, pass fit="container"; the parent needs a definite block size. Every region except the sidebar is a named inline-size container: app-shell-navbar, app-shell-banner, app-shell-main and app-shell-dock. Write @container app-shell-main (min-width: 60rem) and a page answers the width of main, not the window.
As the root layout of a TanStack Router app
AppShell needs no router. main renders whatever it is given, so the root route passes <Outlet />. Navbar tabs and Sidebar rows take the current route from the router through props they already have:
  • NavbarTabs takes currentPath, navigate and matchPath. Sidebar takes no path: the caller sets active on the row for the current page and navigates in onClick.
  • main is the scroll container, so the router's element scroll restoration has to find it. Pass mainData={{ "data-scroll-restoration-id": id }} and create the router with scrollRestoration: true and scrollToTopSelectors: ['[data-scroll-restoration-id="id"]']. A page you leave and return to gets its scroll position back; a new page starts at the top. mainRef hands you the element for anything else that needs it.
  • @tanstack/react-router is not a dependency of @prepared911/ui. It is a devDependency of this repository, for the story and the browser test.
Route changes
By default a route change leaves focus where it is, so a keyboard user who pressed a tab stays on the bar. Two props change what a page change does:
  • routeKey is the identity of the page (usually the pathname). With routeFocus={AppShellRouteFocus.Main} a change after the first render moves focus to main, which starts a reader at the new page.
  • routeAnnouncement is the name of the page. It is announced politely when it changes, whatever happens to focus.
Skip link
A skip link named by skipLinkLabel is the first control in the tab order. It is hidden until it has focus, then shown over the bar. Activating it moves focus to main and does not change the address.
Narrow windows and zoom
Below the 1080px viewport floor the shell still does not reflow its regions (AS-19), but the header does (AS-21). Put the bar's actions in a Toolbar with overflowLabel inside NavbarEnd so they collapse into the overflow menu at 360px width and at 400% zoom (320 CSS px), with every action reachable by keyboard. NavbarStart does not shrink; keep it short. See Toolbar usage and Navbar usage.
Not here
PanelWorkspace, mobile navigation (the viewport floor is 1080px wide) and TakeoverPage (a separate composition) are not part of the shell. The shell imports no ControlIsland either: the role-based island arrives through navbar, which a host fills with an App Header (its island shows the outlined raise hand while lowered and the filled hand while raised).
Previous
Crud Page / Accessibility
Next
App Shell / API and Development
On this page
Regions
As the root layout of a TanStack Router app
Route changes
Skip link
Narrow windows and zoom
Not here