Resolved: light

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 · 672px
size="content"max-w-4xl · 896px
size="wide"max-w-6xl · 1152px

Frames are left-aligned here to compare widths; the real PageContainer is mx-auto (centered in the content column).

form / content / wide — relative max-widths, to scale
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.

active: "Overview" (first tab) — aria-current="page"
active: "Members" (middle tab)
active: "Settings" (last tab)
no active tab — every link muted (hover one for the underline cue)
many tabs — overflows to a horizontal scroll (no wrap)
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

size="md" — plain label

Go to

size="sm" — plain label

Your tasks

size="md" — with left icon

Your tasks

size="sm" — with left icon

Your communities

3 teams
with hint (right-aligned text)

Notifications

with hint as an action link

Announcements

2 new
hint as a Badge

Your representatives

By address
...card body sits below the padded header band.
padded — flush header atop a Card padding="none" body
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.

  1. Sign up
  2. Profile
  3. Your address
  4. Find a team
horizontal (default) — step 3 of 4 current
  1. Apply
  2. Review
  3. Done
horizontal — all complete
  1. Apply
  2. Review
  3. Done
horizontal — first step current, rest pending
  1. Sign up
  2. Profile
  3. Your address
  4. Find a team
orientation="vertical"
  1. Confirm details
  2. Submit
vertical — node label (ariaLabel supplied)
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".