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.

In-app

The mark owns the corner, solo. You're already inside — it just keeps a bit of gold in view.

Acquisition

The full lockup crowns the centered task — a Portal has no app bar, so it names who you're signing in to.

Clear space
Keep clear space ≥ the mark's own height on every side. Nothing — text, control, or edge — crowds it.
Minimum size
mark · 24pxlockup · 20px
Below the lockup's floor, drop to the mark — the resolution rule doubles as the small-size rule.
Never
  • 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.

Halo
app-bounded

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.

LanguageSelector
Click the trigger to see the popover with English / Español. The choice persists to a cookie and updates <html lang>.

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.

Halo

The avatar is the whole control — open it and the account menu drops. No chevron; the circle is the tell.

Three states

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.

In the menu

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.

Never
  • 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) => void

Telemetry 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
ReactNode

Expected 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
ReactNode

Whatever convention mounts in this slot. Self-isolated via an internal ErrorBoundary that reports through onSlotError on the root <AppBar>.

…native attrs
HTMLAttributes<HTMLElement>

Standard HTML attributes pass through to the underlying slot container <div>.

Usage

Hand-composed Reference shelltsx
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>
Same shell, via the canonical presettsx
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.
  • /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.