Site Layout
How a site sits on the universal surface (ADR-0028). Every site has a job — Promotion, Reference, or Application — but they all land on just two layouts, because layout is about arrangement, not content. application gives you a left rail and a content well (Application and Reference both use it — only the rail's content changes); promotion moves the nav into the app bar, adds a footer, and goes full-bleed.
SiteLayout takes care of the chrome for you: it mounts the AppBar at the top, hosts the rail (or footer), wraps your page content in ContentWell, and drops in the two always-there corners (PreferencesCorner bottom-left, CommsCorner bottom-right). It also paints the page splash onto <body>. You bring the bar contents and the rail (or footer) — SiteLayout handles the rest.
You're looking at it
This design site is a real, running <SiteLayout variant="application"> — gold app bar up top, the doc TOC in the left rail, this content well in the middle, and the two silver corners holding down bottom-left and bottom-right. The map below labels the four-corner setup from ADR-0027 (register × audience):
Variants
Just two. Audience, permissions, and data don't earn you new layouts — internal support, customer admin, tenant admin, and platform admin all run the Application layout, and only authorization and configuration differ. Reference uses it too, swapping app nav for a doc TOC.
application
Left nav rail + content well. The rail's content differs by site purpose (app nav for Application, doc TOC for Reference, filter rail for analytical variants) — that's content, not layout.
promotion
Full-bleed content + footer sitemap; nav moves into the AppBar middle slot. No rail.
Page-level ErrorBoundary — baked in
SiteLayout wraps the route children in an <ErrorBoundary> with <ErrorState scope="page"> as the fallback. Consumers don't add the page-level boundary themselves — it ships with the Layout. AppBar, PreferencesCorner, and CommsCorner all sit OUTSIDE this boundary and self-isolate independently, so a route crash leaves the brand, the sign-in path, the preferences, and the human-comms door all reachable (per the placement doctrine in /error-handling).
<ContentWell>
<ErrorBoundary fallback={<ErrorState scope="page" />}>
{children}
</ErrorBoundary>
</ContentWell>Usage
import {
AccountSlot,
AppBar,
BrandSlot,
Scroll,
SiteLayout,
} from '@halo-compliance/ui'
export function AppShell({ children }) {
return (
<SiteLayout
variant="application"
appBar={
<AppBar>
<AppBar.Brand>
<BrandSlot variant="lockup" />
</AppBar.Brand>
<AppBar.Middle />
<AppBar.End>
<AccountSlot state="signed-out" signInHref="/sign-in" />
</AppBar.End>
</AppBar>
}
rail={<DocTOC />}
comms={{ mailto: 'hello@halocompliance.com', version: 'v1.0.0' }}
>
<Scroll>{children}</Scroll>
</SiteLayout>
)
}Compositional parts
SiteLayout's slots are passed as props. The AppBar is a required slot; rail and footer are variant-conditional via the discriminated-union types.
- variant
application | promotionWhich layout. application is the default; covers Application and Reference sites.
- appBar
ReactNodeThe configured <AppBar> with its three slots filled. Required.
- rail
ReactNodeLeft nav rail content. Required for application; rejected for promotion.
- footer
ReactNodeFooter sitemap. Allowed for promotion; rejected for application.
- comms
{ mailto, subjectPrefix?, version? }CommsCorner config — pass-through for the mailto fallback.
- cookieDomain
stringPass-through to PreferencesCorner. Set to .halocompliance.com in production for cross-subdomain prefs.
- children
ReactNodePage content — typically a <Scroll> template inside.
When to use which
- application
- Your default. Use it for any work surface — Application sites, Reference sites (docs, runbook), and BI or analytical variants. What changes is what goes in the rail, not the arrangement.
- promotion
- Use it for marketing — the homepage, product pages, pricing. Full-bleed pages are happier without a rail; the top nav rides in AppBar.Middle and the sitemap lives in the footer.
Anti-patterns
- Don't invent a third layout to support an audience. Internal support, customer admin, tenant admin, and platform admin are all the Application layout. Audience, permissions, and data don't multiply layouts (ADR-0028).
- Don't hand-roll the corners. Preferences and Comms are part of the universal surface — SiteLayout includes them automatically. Adding them again, or replacing them with bespoke chrome, breaks the invariant per ADR-0027.
- Don't make the shell scroll. SiteLayout is viewport-locked; only the ContentWell scrolls internally. Page-level scroll defeats the pinned AppBar and rail. If a page needs more height, use <Scroll> inside the well.
- Don't use SiteLayout for the sign-in door. Portals get their own Layout — no AppBar, the brand sits above the centered sign-in task, and the two bottom corners are still there.
Related
- /templates — Scroll template; what mounts inside the content well.
- /app-bar — the universal-surface spine: brand slot, middle (shell-conditional), language, account & auth.
- /preferences, /comms — the two bottom-corner chrome patterns SiteLayout composes.
- /error-handling — the placement doctrine for the baked-in boundary.