Resolved: light

App chrome (shell)

The frame that wraps every page: a top header bar (brand wordmark + a few nav links + a primary action + a notification count), a left nav rail of grouped, icon'd links with an active you-are-here state, and a footer — plus an 'example portal' card showing PageTabs over a content area. Everything below is a STATIC MOCK: it rebuilds the look of the real Header / PortalShell with plain markup and the shared UI primitives rather than importing the originals (which are client components bound to auth + live data). Each piece reads its colours from the Save the Dogs semantic tokens, so flip the theme in the top bar to confirm the whole frame re-skins in light and dark.

what this is
A presentational reference for the app frame — header bar, left rail, footer, and a portal content card. Static markup + shared primitives; no auth, no data, no navigation.
why a mock
The real chrome (Header.tsx, PortalShell.tsx, PortalSidebar.tsx) is "use client" and pulls session + notification counts. This page reproduces their exact class treatments instead, so it stays public and theme-only.
real primitives used
Button (the header CTA), NotificationBadge (the count chip), Badge, and PageTabs (the portal card sub-nav) are the genuine @/components/ui components — only the layout chrome around them is mocked.
active state
Header links use a border-accent underline (border-b-2); rail links use a soft accent wash (bg-indigo-50 dark:bg-indigo-900/20) + text-accent. Both also set aria-current="page" — never colour alone.
theme-aware
Surfaces (bg-surface / -2 / -3), borders (--color-border), text (--color-text / muted / faint) and the accent are all semantic tokens, so the frame re-skins with the theme switch with no per-element edits.

The full frame — header + rail + content + footer

The whole chrome assembled at once, exactly as a page wears it: a sticky-style header bar across the top, the left nav rail down the side, a content well in the middle (here holding the example portal card), and the footer pinned beneath. Resize / theme-flip to see every surface tier respond. This is a self-contained mock — it does not nest a second <main> or sidebar inside the styleguide's own layout; it's a bordered preview of the frame.

Your dashboard

Everything you need to get involved in your community.

Downtown organizing team

Your hub for this community — agreements, reports and updates.

Active
content area · showing "Overview"
128
Members
4
Upcoming events
2
Open agreements

This panel is a static stand-in for the routed content behind the active tab — in the real portal each PageTab is its own URL, so the panel is whatever that page renders.

app chrome — header · left rail · content · footer (static)
header bar
border-b border-[--color-border] over bg-surface; left = wordmark + nav links, right (ml-auto) = a notification bell carrying a NotificationBadge + the primary accent Button. In production it's sticky (sticky top-0 z-30).
left rail
w-56 aside, border-r, bg-surface, hidden below md (a slide-over drawer takes over on mobile). Holds the grouped <nav> of icon'd links.
content well
min-w-0 flex-1 over bg-[--color-bg] — the page's reading column. In the real shell this is where PageContainer (the <main>) + the page body render.
footer
border-t bg-surface, text-faint; a copyright line + a small link nav. Stacks on mobile, splits left/right from sm up.

Header bar — wordmark, nav links, actions

The top chrome on its own, with each part labelled. The wordmark sits in the Fraunces display face; the nav links use a 2px underline for the active page (border-accent) and a muted-with-hover treatment otherwise; the right cluster carries a notification bell with the real NotificationBadge count chip and the primary accent Button. Every nav link also sets aria-current on the active one, so the you-are-here cue is never colour alone.

header bar — active link 'Home', 3 unread, Create CTA

brand wordmark (font-display)

nav link — active (border-accent underline + aria-current) vs idle (muted + hover)

NotificationBadge — tones & clamping (count=0 renders nothing)

count=3
count=12
clamps to 99+
tone="accent"
tone="gray"
count=0 → null

header primary action — Button variant="primary" (size sm)

states — wordmark · active vs idle nav link · NotificationBadge counts
wordmark
font-display text-base font-bold tracking-tight, in --color-text. Links home. The styleguide sets it in Fraunces; reproduce that for the Save the Dogs brand.
nav link — active
h-11 px-3 border-b-2 border-accent, text-[--color-text], font-medium, aria-current="page". The 2px accent underline is the primary you-are-here cue.
nav link — idle
border-transparent, text-muted, hover:text-[--color-text]. No underline until active. Keep these as real links (or buttons for menus).
NotificationBadge
The real count chip: count<=0 renders null; clamps to max+ (default 99); tone red (default) / accent / gray; pass a label fn for the aria-label. Position it -top-0.5 -right-0.5 over the bell.
primary action
A Button variant="primary" size="sm" — the accent CTA for the whole app (e.g. Create). Pair an icon (h-4 w-4) before the label.

Example portal card — PageTabs over a content area

A small portal surface that shows the canonical in-page sub-nav pattern: a header band, PageTabs (Overview / Agreements / Reports) as the secondary nav, and the content area beneath. PageTabs is the real primitive — a links-based nav (not a WAI-ARIA tablist), so the active tab carries an accent underline + aria-current="page" and each tab is a real URL. The demo uses anchor (#…) hrefs so nothing navigates; the panel below simply reflects what the "Overview" tab would show.

Downtown organizing team

Your hub for this community — agreements, reports and updates.

Active
content area · showing "Overview"
128
Members
4
Upcoming events
2
Open agreements

This panel is a static stand-in for the routed content behind the active tab — in the real portal each PageTab is its own URL, so the panel is whatever that page renders.

portal card — PageTabs active "Overview"

Downtown organizing team

Your hub for this community — agreements, reports and updates.

Active
content area · showing "Agreements"
128
Members
4
Upcoming events
2
Open agreements

This panel is a static stand-in for the routed content behind the active tab — in the real portal each PageTab is its own URL, so the panel is whatever that page renders.

same card, active tab moved to "Agreements"
header band
A bordered top strip (px-5 py-4) with the card title in the display face + a short muted sub-line. Sits flush above the tab bar.
PageTabs
The real @/components/ui PageTabs: tabs = [{ label, href, active? }]. The active tab gets a 2px accent underline + text-accent + aria-current="page"; the others are muted with a hover underline. It is link nav, NOT role="tablist".
content area
The panel below the tabs (p-5). In production this is the routed page body for the active tab's URL; here it's a static stand-in for the Overview view.
not a tablist
Because each tab is a separate URL (bookmarkable, back-button friendly), this is a <nav aria-label> + aria-current pattern — there is no client-toggled tabpanel or arrow-key roving focus to justify role=tablist.