PageHeader — the page title block
The standard top-of-page heading: a font-display (Fraunces) bold <h1> title, an optional muted description beneath it, and an optional right-aligned actions slot. When actions are present the block becomes a flex row (title left, actions right, items-start); without them it's a plain stacked block. There should be exactly one PageHeader — one <h1> — per page.
Your dashboard
Your dashboard
Everything you need to get involved in your community.
Your teams
Communities you've joined and ones we think you'll like.
Members
Everyone on the Downtown organizing team.
- title
- string (required). Rendered as a font-display text-xl font-bold <h1> in the primary text colour. The page's single top-level heading.
- description
- string (optional). A muted text-sm sub-line beneath the title (mt-0.5). Omit it for a bare title block.
- actions
- ReactNode (optional). Right-aligned, shrink-0. When present the whole block becomes flex items-start justify-between gap-4. Put a Button (or a small group) here — e.g. a primary CTA for the page.
- spacing
- mb-6 below the block. Pair it directly under PageContainer's top inset so the title sits at the page's reading start.
PageContainer — page width frames
The canonical page wrapper. It renders the <main> landmark, centered (mx-auto w-full) with px-4 md:px-6 py-6 padding and a max-width chosen by the size prop: form (max-w-2xl) · content (max-w-4xl, the default) · wide (max-w-6xl). The frames below mirror those exact width classes so you can see the relative measures — they're labelled <div>s, NOT real PageContainers, because nesting a second <main> inside this styleguide's <main> would be invalid. Use exactly one PageContainer per route.
size="form"max-w-2xl · 672pxsize="content"max-w-4xl · 896pxsize="wide"max-w-6xl · 1152pxFrames are left-aligned here to compare widths; the real PageContainer is mx-auto (centered in the content column).
- size="form"
- size="form" — max-w-2xl (672px). Keeps form fields at a readable measure: signup, settings, single-column edit pages.
- size="content"
- size="content" — max-w-4xl (896px). The DEFAULT. The everyday portal reading width: dashboards, article-style pages, mixed content.
- size="wide"
- size="wide" — max-w-6xl (1152px). Dense dashboards & tables that need the horizontal room (this very styleguide uses size="wide").
- padding
- mx-auto w-full px-4 md:px-6 py-6 — real horizontal padding that removes the dead zone between the sidebar and the content edge; vertical py-6 sets the page's top/bottom inset.
- landmark
- Renders a <main> element (the page's main landmark). It also spreads HTMLAttributes<HTMLElement>, so className and other <main> props pass through. Exactly one per route — never nest two <main>s.
PageTabs — links-based secondary nav
A compact horizontal nav for a page's sub-sections. Each tab is a real next/link to a SEPARATE URL — bookmarkable, shareable, back-button friendly — so this is NOT a WAI-ARIA tablist: it's a nav landmark (a <nav aria-label>) whose active link carries aria-current="page". The active tab gets a 2px accent underline + accent label (text-accent, which re-skins per theme); the rest are muted with a hover underline. The row scrolls horizontally on overflow (mobile) rather than wrapping. The demos below use href="#…" anchors, so clicking a tab won't navigate.
- tabs
- PageTab[] (required), left to right. Each: { label: string; href: string; active?: boolean }. href is an internal next/link destination — each tab is its own URL.
- tab.active
- boolean. The current page's tab. Gets border-accent text-accent (the 2px accent underline + accent label) and aria-current="page". Mark exactly one tab active per page.
- aria-label
- string (default "Sections"). The accessible name for the <nav>. Give it something specific (e.g. "Team sections") so AT users can tell tab bars apart.
- not a tablist
- Deliberately NOT role=tablist / role=tab / aria-selected — there is no client-toggled tabpanel or arrow-key roving focus. It's link navigation, so it uses the <nav> + aria-current pattern instead.
- overflow
- Single row; on overflow it scrolls horizontally (scrollbar hidden) rather than wrapping — so a long tab set stays one tidy line on mobile.
SectionHeader — in-card section label
The portal's canonical small section label: an uppercase, tracking-wide, muted (text-faint) h2 element, optionally with a left icon and a right-aligned hint/action. It's the header band of a CompactCard and the little label above grouped lists. Two sizes — md (default) and sm (tighter, for dense groupings) — and a padded flag that adds the px-4 pt-4 inset for sitting flush atop a Card padding="none" body. Because it's a real h2, it nests under the page's h1 in the outline.
Go to
Go to
Your tasks
Your tasks
Your communities
Notifications
Announcements
Your representatives
- children
- ReactNode (required). The label text — usually a short uppercase-cased string. Rendered as an <h2> (uppercase tracking-wide text-faint), so it slots under the page <h1>.
- icon
- ReactNode (optional). A left icon, already sized to the chosen size (md → h-3.5 w-3.5, sm → h-3 w-3). Mark it aria-hidden — the label text carries the meaning.
- hint
- ReactNode (optional). A right-aligned text-xs muted slot for a count, a Badge, or a "View all →" action link. shrink-0, so it hugs the right edge.
- size
- size="md" — the DEFAULT. Label text-[11px], icon box h-3.5 w-3.5. The standard card / "Go to" section label. size="sm" — tighter. Label text-[10px], icon box h-3 w-3. For dense groupings where the md label reads a touch too tall.
- padded
- boolean (default false). Adds px-4 pt-4 so the header sits flush at the top of a Card padding="none" body. Leave off when it lives inside an already-padded surface.
- className
- Optional extra classes merged onto the outer flex row (e.g. extra bottom spacing). Everything else stays as the canonical label treatment.
ProgressSteps — step indicator
A numbered step indicator for a multi-step flow (onboarding, an application). Rendered as an ordered list of circles joined by connectors, each step completed / current / pending — completed shows a check, current is the accent circle with the live number, pending is a muted outline. The current step carries aria-current="step" and every step has an accessible label with a status suffix (e.g. "Apply, completed"). Runs horizontal (default) or vertical. State is never on colour alone — the check glyph + per-step status text carry it for AT.
- Sign up
- Profile
- Your address
- Find a team
- Apply
- Review
- Done
- Apply
- Review
- Done
- Sign up
- Profile
- Your address
- Find a team
- Confirm details
- Submit
- steps
- ProgressStep[] (required). Each: { label: ReactNode; status: "completed" | "current" | "pending"; ariaLabel?: string }. Rendered left-to-right (or top-down) as the run.
- step.status
- completed → accent circle + check glyph; current → accent circle + the live number (aria-current="step"); pending → muted outline circle. Connectors after a completed step turn accent too.
- step.ariaLabel
- Accessible per-step label. Falls back to the string label + a status suffix ("Apply, completed"). Provide it explicitly when label is a non-string node (e.g. an icon + text).
- orientation
- horizontal (default) — circles in a row with horizontal connectors and centered labels; vertical — a top-down run with the label beside each circle.
- aria-label
- string (default "Progress"). Names the whole <ol> (the process). Give it the flow's name, e.g. "Onboarding progress".