AppShell
The page frame: header, optional subnav, main and footer.
Overview
Every page of an app sits in one AppShell; it is the only place the page canvas, the font and the header live.
Brand
import { Avatar, AppShellBrand } from "@hackcove/ui";
<AppShellBrand href="#" logo={<Avatar name="Quotes" size="sm" />} label="Quotes" collapse />AppShellBrand makes the logo and the label one link, so the whole brand is the way home; collapse leaves only the logo below sm.import { Avatar, Cluster, TextLink } from "@hackcove/ui";
<Cluster gap="xs">
<a href="#">
<Avatar name="Quotes" size="sm" />
</a>
<TextLink href="#">Quotes</TextLink>
</Cluster>AppShellBrand.Router brand
import { Avatar, AppShellBrand } from "@hackcove/ui";
<AppShellBrand
logo={<Avatar name="Quotes" size="sm" />}
label="Quotes"
render={(props) => <a {...props} href="#home" />}
/>render draws the app's own link and AppShellBrand adds the logo, the label and the focus ring (here a stand-in <a>).Usage
- Fill the slots and write no wrapper:
brand,nav(aNavTabs),controls(a picker, aColourSchemeToggle), then an optionalsubnavrow andfooter. brandis anAppShellBrand: logo and label in one link.collapseleaves only the logo belowsm.- With a router, give
AppShellBrandarenderthat draws the app's own<Link>instead of anhref. - Header, main and footer each sit in a
Container, so a page never sets its own width or padding. Do not add aContainerinside the shell. bleeddrops main'sContainerfor a layout that needs the whole width (FullBleedLayout);SidebarLayoutneeds nobleed.hideChromeInPrinthides the header and footer and main's padding on paper; screen is unchanged.headeris for a node no other slot covers. Preferbrand,navandcontrols.- Put
appShellRootClassFor(scheme)on the app's own<html>.AppShellnever touches<html>. - A theme sets the canvas colour and the font; the app writes no page background or font rule.
Accessibility
- The header is a
<header>(the banner landmark), main is<main>and the footer is<footer>. AppShellBrandis one link named by itslabel; the logo isaria-hidden. Withcollapsethe label is visually hidden belowsmand still names the link.- A header row that is wider than the screen (a long nav) scrolls sideways and never wraps; a link that takes focus scrolls into view, and the side with more to reach fades out.
- Give
navandsubnavaNavTabsso each is a labelled<nav>.
API
AppShell
The page frame: it paints the page canvas and sets the type face and ink, so an app writes no page-background or font rule of its own (a theme remaps the canvas and the font stacks). A sticky one-row header, an optional second subnav row, the main area and an optional footer. Header, main and footer each sit in a Container, so pages never set their own width or padding. bleed drops main's Container (the header keeps its own), for a layout that needs the whole width (FullBleedLayout). Main is a flex column. SidebarLayout needs no bleed: its rail and canvas share the header's Container. The header row is laid out by its slots, left to right: brand, nav, then header (anything the slots do not cover), then controls, pushed to the end. An app writes no wrapper or styling for any of them.
bleedMain without a Container, for a full-bleed layout.
- Type
boolean- Default
false
brandThe brand: a logo and a name, in the header's brand type, with the gap between them. AppShellBrand makes both one link.
- Type
ReactNode
controlsThe end of the row: a picker, a theme toggle. Gap-2, vertically centred; no child shrinks, and a full-width control (a Select at its default) sizes to its content.
- Type
ReactNode
footer- Type
ReactNode
headerThe escape hatch: a free node in the header row, after brand and nav.
- Type
ReactNode
hideChromeInPrintPrint only the page: the header and footer are hidden, and main's Container drops its padding
(the page margin is then @page's). Off by default, so a print is unchanged unless an app asks.
- Type
boolean- Default
false
navThe main navigation, e.g. NavTabs (it is already a <nav>).
- Type
ReactNode
subnavA second fixed-height row under the header, e.g. NavTabs.
- Type
ReactNode
AppShellBrand
The brand slot as one link: a logo and a label inside the same <a>, so the whole brand is the way home. The label names the link; the logo is decoration and hidden from assistive tech. collapse shows only the logo below sm, the label staying in the accessible name. With render it draws the app's own router <Link> (render={(p) => <Link to="/" {...p} />}).
collapseBelow sm only the logo shows; the label is visually hidden and still names the link. Needs a logo.
- Type
boolean- Default
false
href- Type
string
label requiredThe name beside the logo. It is the link's accessible name.
- Type
ReactNode
logoThe mark before the label, e.g. an Avatar or an <img>.
- Type
ReactNode
render- Type
LinkRender
Functions
appShellRootClassThe class for the app's own<html>that always reserves the scrollbar, so centred content does not shift between a short page and a long one.appShellRootClassForEverything the app's<html>takes from the scheme the server knows: that scheme's class andappShellRootClass, joined.