AppBar
The universal-surface chrome spine (ADR-0027). A horizontal three-slot bar that owns the full top of the viewport and both top corners. Under the official brand it's a flat, solid surface — opaque fill, hairline bottom edge — with the gold mark holding the left corner. The engineered-glass treatment (translucency, backdrop blur, specular) belongs to the proposed system; here the bar just sits on top — solid, friendly, out of your way. The component is the shape; the slots are geometry; what mounts inside each slot is convention — covered in the four sections below.
Compound API: <AppBar.Brand> (top-left, fixed width), <AppBar.Middle> (flex-grow, centered content), <AppBar.End> (top-right, fixed width — holds language + account). Each slot self-isolates per ADR-0030: a failure inside one slot's children renders a silhouette-preserving placeholder rather than collapsing the bar's grid. Two modes — edge (the production viewport-edge mode, no bezel) and bounded (embedded contexts: doc demos, marketing previews — a bezel terminates the bar within its container).
For the canonical Reference-shell composition that bakes language + account into the End slot and takes middle + account as composition, see /reference-app-bar.
Slots are geometry; content is convention
The four sections that follow document what mounts in each slot — the canonical Halo conventions. The slots themselves are just geometry: <AppBar.Brand> is a fixed-width left cell, <AppBar.Middle> is a flex-grow centered cell, <AppBar.End> is a fixed-width right cell. The same component can hold a completely different chrome composition on a future shell; the bar's shape doesn't change.
Brand slot
The most invariant chrome: the top-left of the app bar, present on every surface. It resolves between two forms by context — the mark alone in-app, where you already know you're at Halo and space is precious; the full lockup on acquisition surfaces (sign-in, marketing), where identity is still being established. One slot, two forms, chosen by surface — never both, never neither.
The mark owns the corner, solo. You're already inside — it just keeps a bit of gold in view.
- recolor or re-gradient it
- stretch, rotate, or skew
- add shadows or effects
- show both forms at once
This is the sole brand-mark exception to the stroke-icon rule (ADR-0010): everywhere else brand is not a UI icon. The mark is solid gold — flat, not engraved (engraving belongs to the proposed system) — and it never recolors. Same courtesy a third-party SSO mark gets.
Middle
The slice between brand and account — the one region of the bar that's app-bounded: the shell owns its content, and chrome only provides the slot. It's the bar's single conditional region; brand, language, and account are invariant on every surface. The controls inside are components, not chrome.
The Reference (docs) shell needs only search. The trigger above is the real ReferenceMiddle bound to this site's actual search index; click it (or trigger ⌘K from anywhere) to see the canonical CommandPalette open over the page.
Language
Top-bar invariant chrome, just left of the End slot's account affordance. Language is a content pref — it changes what's rendered, and it matters from first paint, before auth. That's why it deliberately doesn't live in the bottom-left Preferences corner with the device prefs (appearance, motion, contrast). One entry point per affordance: language is here, on every shell, on every surface.
Bilingual US per the project's i18n posture: English and Español as the initial values. Cookie-backed across .halocompliance.com subdomains so the choice follows the visitor and paints correctly at SSR first load. The selected language also sets <html lang> so screen readers and downstream content can resolve it.
See /preferences for why language is not in the device-prefs popover.
Account & auth
The top-right gold corner — the you pole of the lattice, paired with Preferences at bottom-left. It's avatar-only: no dropdown chevron, because the avatar is the affordance. What it shows resolves by auth context across three states, and it never renders an empty or broken corner when you're signed out.
The avatar is the whole control — open it and the account menu drops. No chevron; the circle is the tell.
Authenticated → the avatar opens the account menu. Authenticating → a quiet placeholder, never a flash of signed-out. Signed out → "Sign in" to the Portal; in-app an expired session redirects (401 → log out). The AuthContext contract.
Identity (name · email), Account, Sign out — the account, nothing else. Appearance lives in Preferences (bottom-left); language at the top of the bar; help in Comms (bottom-right); org switching in the Tenant switcher.
- a dropdown chevron — the avatar is the tell
- an empty state when unauthenticated — redirect
- device prefs or help crammed in here
Resolves by auth context, avatar-only, never empty — the AuthContext pattern made visible. It anchors the you diagonal (account top-right ↔ preferences bottom-left); the us diagonal is brand (top-left) ↔ comms (bottom-right). The menu is the only place the account itself is operated; the other corners own everything else.
API
The component and its three compound slots.
<AppBar>
- mode
'edge' | 'bounded'Default
'edge'. Edge is the production viewport-edge mode (no bezel); bounded adds a hairline frame so the bar terminates within an embedded container.- onSlotError
(error: Error, info: ErrorInfo) => voidTelemetry reporter — fires when any of the per-slot boundaries catch. Propagated via context to all three slots, so consumers wire it once on the root.
- children
ReactNodeExpected to be the three compound slots:
<AppBar.Brand>,<AppBar.Middle>,<AppBar.End>. Any of them may be omitted — the slot just renders as a 0-width region.- …native attrs
HTMLAttributes<HTMLElement>Standard HTML attributes pass through to the underlying
<header>.
<AppBar.Brand> / <AppBar.Middle> / <AppBar.End>
- children
ReactNodeWhatever convention mounts in this slot. Self-isolated via an internal ErrorBoundary that reports through
onSlotErroron the root<AppBar>.- …native attrs
HTMLAttributes<HTMLElement>Standard HTML attributes pass through to the underlying slot container
<div>.
Usage
import { AppBar, BrandSlot, LanguageSelector, AccountSlot } from '@halo-compliance/ui'
<AppBar onSlotError={reportChromeError}>
<AppBar.Brand>
<BrandSlot variant="mark" href="/" />
</AppBar.Brand>
<AppBar.Middle>
<ReferenceMiddle items={SEARCH_INDEX} onNavigate={navigate} />
</AppBar.Middle>
<AppBar.End>
<LanguageSelector />
<AccountSlot state="signed-out" signInHref={portalUrl} />
</AppBar.End>
</AppBar>import { ReferenceAppBar, ReferenceMiddle, AccountSlot } from '@halo-compliance/ui'
<ReferenceAppBar
brand="mark"
middle={<ReferenceMiddle items={SEARCH_INDEX} onNavigate={navigate} />}
account={<AccountSlot state="signed-out" signInHref={portalUrl} />}
onSlotError={reportChromeError}
onChromeError={reportChromeError}
/>Anti-patterns
- Don't break the slot geometry. Brand at top-left, account at top-right, language just left of account, middle in between — same shape on every shell. Inverting or moving the corners destroys the cross-product wayfinding (each corner is one quadrant of the device-/content × you-/us lattice).
- Don't put device prefs or comms in the bar. Those live in the bottom corners (PreferencesCorner bottom-left, CommsCorner bottom-right). Crowding them up here breaks the four-quadrant lattice and steals attention from the work surface.
- Don't cram the middle. The middle is shell-conditional but tightly scoped — Reference: search only. App: ⌘K command + tenant switcher + alerts. Promotion: nav links. Anything beyond the shell's canonical set belongs in the rail or content well, not the bar.
- Don't render an empty End slot. Even signed-out, the End slot shows a Sign-in button. The bar's silhouette is invariant.
Related
- /reference-app-bar — the canonical Reference shell composition: bakes language, takes brand variant + middle + account as props.
- /preferences — bottom-left silver corner; device prefs (appearance, motion, contrast). Language is intentionally not here.
- /comms — bottom-right silver corner; the human-comms door.
- /portal — the surface that has no app bar; brand crowns a centered task. Language stays.
- /shell — how the bar mounts inside SiteLayout.
- /card — the surface the bar shares. In the official brand the Card is flat and opaque — solid fill, soft shadow, no glass or specular (those belong to the proposed system). The bar is that same surface, shaped as the top spine.