Layout · width
Container
Centers content and caps line length with consistent gutters. Four widths: narrow (reading), default, wide (dashboards), and full. Gutters tighten on small screens.
The hatched band is the viewport; the solid block is the container. Notice the gutters hold as the cap changes.
Semantics & a11y
- Purely presentational <div>; landmarks (main, nav) live on Section, not here.
- narrow keeps measure near 60–75 characters for readability.
- Gutters use padding-inline so they never collapse on nested containers.
Token contract
// Container.tsx
type Props = { size?: "narrow" | "default" | "wide" | "full"; className?: string; children: React.ReactNode };
const max = { narrow: "max-w-[var(--container-narrow)]", default: "max-w-[var(--container-default)]", wide: "max-w-[var(--container-wide)]", full: "max-w-none" };
export const Container = ({ size = "default", className = "", children }: Props) =>
<div className={`w-full mx-auto px-[var(--space-6)] ${max[size]} ${className}`}>{children}</div>;Layout · rhythm + tone
Section
A vertical band with consistent block padding and an optional tone: default, surface, accent, or inverted (dark ground, light text). Density adjusts the rhythm: compact, regular, generous.
Default tone
Page background
Inherits the canvas. The everyday section.
Surface tone
Raised band
Lifts off the page to group related content.
Accent tone
Brand moment
For CTAs and high-emphasis bands.
Inverted tone
Dark ground
Light text on the foreground color, tokens remapped so children invert too.
Semantics & a11y
- Renders <section>; pair with an aria-labelledby heading for landmark navigation.
- Inverted remaps --color-bg/surface/border/muted locally, so nested cards and dividers stay legible without overrides.
- Accent tone relies on the AA-tuned --color-accent-fg per palette.
Token contract
// Section.tsx
const density = { compact: "py-[var(--space-10)]", regular: "py-[var(--space-16)]", generous: "py-[var(--space-24)]" };
const tone = {
default: "",
surface: "bg-[var(--color-surface)]",
accent: "bg-[var(--color-accent)] text-[var(--color-accent-fg)]",
inverted: "bg-[var(--color-fg)] text-white [--color-surface:color-mix(in_srgb,var(--color-fg)_80%,#fff)] [--color-border:color-mix(in_srgb,#fff_22%,transparent)]",
};
export const Section = ({ as:Tag="section", t="default", d="regular", className="", ...p }) =>
<Tag className={`${density[d]} ${tone[t]} ${className}`} {...p} />;Layout · vertical rhythm
Stack
The vertical rhythm primitive: a flex column with a single gap from the space scale. Five steps (xs → xl). Replaces ad-hoc margins so spacing is consistent and editable in one place.
Semantics & a11y
- No semantics of its own; wrap meaningful children (headings, paragraphs, controls).
- Uses gap, not margins, so reordering or removing children never breaks spacing.
- data-align="center" centers and sets text-align for hero stacks.
Token contract
// Stack.tsx — vertical rhythm via a single gap token.
const gap = { xs:"gap-[var(--space-2)]", sm:"gap-[var(--space-3)]", md:"gap-[var(--space-6)]", lg:"gap-[var(--space-10)]", xl:"gap-[var(--space-16)]" };
export const Stack = ({ gap:g="md", align, className="", ...p }) =>
<div data-align={align} className={`flex flex-col ${gap[g]} data-[align=center]:items-center data-[align=center]:text-center ${className}`} {...p} />;Layout · horizontal rhythm
Inline
The horizontal counterpart to Stack: a wrapping flex row with a gap token and justify options. For button rows, tag lists, metadata, and toolbars, never bare inline-block siblings.
Semantics & a11y
- Wraps by default so rows never overflow on small screens.
- Preferred over inline-block runs: gap spacing survives drag-reorder and deletion.
- For toolbars, add role="toolbar" on the consuming element.
Token contract
// Inline.tsx — horizontal rhythm, wraps by default.
const gap = { sm:"gap-[var(--space-2)]", md:"gap-[var(--space-4)]", lg:"gap-[var(--space-8)]" };
export const Inline = ({ gap:g="md", justify, className="", ...p }) =>
<div data-justify={justify} className={`flex flex-wrap items-center ${gap[g]}
data-[justify=between]:justify-between data-[justify=center]:justify-center ${className}`} {...p} />;Layout · 12 columns
Grid
A responsive 12-column grid with a gap token. Children span columns (col-6, col-4…) and collapse toward one column as space tightens. Toggle Mobile above to watch it fold.
Semantics & a11y
- Visual order only; keep DOM order meaningful so tab and reading order match.
- Collapses to fewer columns under 720px and to one under 460px (and in Mobile preview).
- Gap is a token, so grid density matches Stack/Inline across the system.
Token contract
// Grid.tsx — 12-col, responsive, gap from tokens.
export const Grid = ({ gap="md", className="", ...p }) => {
const g = { tight:"gap-[var(--space-3)]", md:"gap-[var(--space-6)]", loose:"gap-[var(--space-10)]" }[gap];
return <div className={`grid grid-cols-12 ${g} ${className}`} {...p} />;
};
// Child: <div className="col-span-12 md:col-span-4"> … </div>