Framework style guide
Tokens & primitives
Every value below is read live from CSS custom properties emitted by framework.scss. Change them in override.scss and this page reflects the update. For reusable layout blocks (hero, sections, cards), see Components.
Colors
10 families × 8 shades. Families inherit base chroma; semantics override for consistent vibrance. Hover tokens are derived at runtime via oklch(from var(--X-medium) …).
primary
secondary
tertiary
accent
base
neutral
success
warning
info
danger
Contrast tokens
--on-{family} is the text color for anything sitting on --{family}-medium(and its hover shade). Picked at build time from the family's medium lightness; a non-flipping primitive, so it stays correct in dark mode where a neutral would invert.
Shadows
Five elevation steps. Ink alpha is multiplied by --shadow-strength at runtime (1 in light mode, stronger in dark) so the same token reads on both surfaces.
--shadow-xs--shadow-sm--shadow-md--shadow-lg--shadow-xlSpacing
Fluid between mobile and desktop via clamp(). Resize the window to see tokens recompute.
Section spacing
Larger vertical rhythm for hero areas and major layout blocks. Desktop ×3, mobile ×2 of the standard spacing scale.
Typography
7 fluid type steps. Base 18px desktop / 16px mobile; scale ratio 1.25 / 1.2. Two display steps sit above the heading ramp: heroes use --display-*, content headings use --h1…--h6 (ramp below).
--display-1The quick brown fox--display-2The quick brown fox--text-2xsThe quick brown fox--text-xsThe quick brown fox--text-smThe quick brown fox--text-mdThe quick brown fox--text-lgThe quick brown fox--text-xlThe quick brown fox--text-2xlThe quick brown foxHeading ramp: --h1…--h6
Real h1–h6 elements with nothing but the base.scss defaults: size from --h*, tight leading on h1/h2 and snug below, tracking on h1/h2. Heroes use --display-* instead, so this ramp stays sized for content.
--h1The quick brown fox
--h2The quick brown fox
--h3The quick brown fox
--h4The quick brown fox
--h5The quick brown fox
--h6The quick brown fox
Leading & tracking
Line-height only via --leading-*, letter-spacing only via --tracking-* — component CSS never uses raw numbers. Samples render the live token.
| Token | Use | Sample |
|---|---|---|
--leading-none | Single-line controls: buttons, badges, icon boxes, toggles. | Two lines of text set at this leading |
--leading-tight | Display sizes and headings. | Two lines of text set at this leading |
--leading-snug | Sub-heads, compact UI titles, multi-line controls. | Two lines of text set at this leading |
--leading-normal | Body copy, ledes, card text. | Two lines of text set at this leading |
--leading-relaxed | Long-form prose. | Two lines of text set at this leading |
--tracking-tight | Display sizes, h1, big stat numbers. | Tracked sample |
--tracking-snug | h2, brand wordmarks, bold UI titles. | Tracked sample |
--tracking-wide | Uppercase eyebrows and labels (the eyebrow mixin). | Tracked sample |
Stacks: .flow / .prose
.flow puts rhythm between stacked siblings (> * + * gets amargin-block-start of --flow-space, with more space above a heading than below it). .prose is .flow plus the readable measure (--measure). Containers trim their last child's margin rather than relying on padding + margin adding up.
A first paragraph inside a .prose block. Its width is capped at the measure, so the line never runs long enough to lose the reader on the way back.
A heading in the flow
The heading gets more space above it than below, which is what groups it with the paragraph that follows rather than the one before.
A third paragraph closes the block. The container trims its bottom margin, so the frame's own padding is the only thing between this line and the edge.
Border radius
--radius-sm--radius-md--radius-lg--radius-xl--radius-2xl--radius-fullSection shell
Section shell
The page building block: a full-bleed outer that owns the gutter and vertical rhythm, an inner wrapper that owns the max-width, and an optional head. This one is tone raised, width wide, default rhythm.
Pages compose Sections inside the bare <main>; the first one clears the fixed header by itself and the closing CTA is rendered by the layout. The width prop mirrors the framework's width ladder:
width | Token | Use |
|---|---|---|
site | --site-width | Default. The header pill's column, so sections line up with it. |
wide | --content-width | One step in from the pill; grids and media that shouldn't touch the site edge. |
content | --content-width-md | Reading / marketing width for text-led sections. |
narrow | --content-width-sm | Hero copy, CTAs. |
prose | --content-width-xs | Long-form text. |
tone="brand" · width="content" · aside slot
A head with an aside
Eyebrow, title and sub stack on the left; whatever fills the aside slot sits beside them, bottom-aligned, and the default slot runs full width underneath.
The aside is for an intro that belongs next to the heading rather than under it. On narrow viewports it drops below the head.
Default-slot content follows the head at the full inner width. Unclassed lists, paragraphs and tables pick up the base defaults here.
Primitive elements
Styled by base.scss — safe defaults for paragraphs, links, lists, quotes, tables and form controls. Headings have their own ramp under Typography.
Lists
- Unclassed lists keep their markers and item spacing.
- Nested lists indent one step.
- Second level
- Still spaced by
li + li
- Classed lists get only the reset and style themselves.
- Ordered lists share the same rules.
- Numbers render in the padding.
Block elements
A quote picks up an inline-start rule, italic, and the dark neutral.
Keys render as keycaps: Ctrl + K. Code stays inline: --space-md.
| Token | Value |
|---|---|
--radius-md | 12px |
--radius-lg | 16px |
Text
A short paragraph of body copy. Line height and margin come from base.scss. Inline links inherit the primary color and switch to the semi-dark shade on hover.
Monospace via code: var(--primary-medium).