Contact Us

Design Tokens

DOC-00004 reference implementor, developer

Design Tokens

The design token system provides a structured way to define, organize, and override visual properties (colors, spacing, typography, layout) across the theme. It uses a three-layer architecture with clear ownership boundaries so core framework tokens and site-specific overrides coexist without conflict.

For the complete token inventory, see the CSS source files in src/core/styles/ and src/site/styles/. This reference documents the system — how it works, how to extend it, and what rules to follow.

Who this is for

  • Implementors building or modifying CSS token files
  • Developers customizing a site’s visual identity via theme overrides
  • AI agents generating component styles that consume tokens

Three-Layer Model

Every design token belongs to one of three layers:

LayerPrefixPurposeExample
Primitive--pt-*Baseline values — scales, sizes, brand hues--pt-space-md, --pt-text-lg, --pt-color-brand-primary
Semantic--st-*Contextual aliases that map to primitives--st-color-brand-primary, --st-font-family-body, --st-font-size-h1
Tailwind Bridge@theme blockRegisters tokens as Tailwind utility classes--color-brand-primary: var(--st-color-brand-primary)

How they connect: Components consume semantic tokens (--st-*) for colors — never --pt-color-* or raw color literals. For non-color properties (spacing, typography scale, etc.), components may reference --pt-* primitives directly. The bridge layer enables Tailwind utility classes to resolve to the project’s token values.

When to create semantic tokens

Semantic tokens for non-color properties are created when:

  • A value is used repeatedly across multiple components (e.g., section spacing)
  • A value represents an obvious client-override point (e.g., heading sizes, button padding)

This is a review-time judgment call, not an automated rule. The semantic inventory grows organically as components reveal patterns.

Token Categories

Tokens are organized into six categories. Each is documented with representative samples — see the CSS source files for the complete inventory.

Color Tokens

Color primitives provide the override surface for site identity. Four groups:

  • Absolutes: --pt-color-black, --pt-color-white, --pt-color-transparent — fixed values for overlays, shadows, and on-inverted text.
  • Neutral ramp: --pt-color-neutral-{50–950} (11 stops) — maps to Tailwind slate by default. Override this ramp to swap the neutral family (e.g., slate → zinc).
  • Status: --pt-color-status-{success,warning,error,info} + -muted (8 tokens) — maps to Tailwind green/amber/red/blue.
  • Brand: --pt-color-brand-{primary,secondary,tertiary} + -emphasis, -subtle (9 tokens) — maps to Tailwind blue/teal/purple.

Semantic color tokens are grouped by purpose:

GroupExample tokensPurpose
Brand fill--st-color-brand-primary, -emphasis, -subtleBrand identity colors used as fills
Brand fg--st-color-brand-primary-fg, -secondary-fg, -tertiary-fgBrand colors used as foregrounds on canvas
Surface--st-color-bg-canvas, --st-color-surface-soft, -invertedBackground/container colors
Text--st-color-text-default, -muted, -on-inverted, --st-color-on-brand-primary, --st-color-text-heading, -heading-on-inverted, -heading-on-contrast, -heading-on-brand-primary, -heading-on-brand-secondary, -heading-on-brand-tertiaryForeground colors for various surfaces
Link--st-color-link, -hover, -visited, -on-contrast, -on-invertedLink colors, including on-surface variants
Action--st-color-action-primary-bg, -fg, -border, plus secondary, tertiary, neutral, inverted, on-primary, on-secondary, and on-tertiary familiesButton/CTA colors
Status--st-color-status-success, -warning, -error, -infoFeedback/alert colors
Navigation--st-color-nav-link, -link-hover, -link-activeNav-specific colors
Form--st-color-form-control-bg, -control-border, -placeholderForm input colors
Utility--st-color-focus-ring, --st-color-overlay-scrim, --st-color-mark-bgMisc UI colors

Every --st-color-* token defined in light mode must also be defined in dark mode.

Brand fill and foreground semantics are separate. Use --st-color-brand-primary, --st-color-brand-secondary, and --st-color-brand-tertiary when the brand color paints an area or decorative fill. Use --st-color-brand-primary-fg, --st-color-brand-secondary-fg, and --st-color-brand-tertiary-fg when the brand color itself must read as foreground on the canvas, including text, borders, outlines, accents, and native control chrome. In light mode the -fg tokens resolve to the brand base hues; in dark mode they resolve to lighter brand primitives so foreground contrast stays readable.

Heading color is a first-class semantic. --st-color-text-heading defaults to var(--st-color-brand-primary) so the override point is visible out of the box. Override it in src/site/styles/light.css and src/site/styles/dark.css to repaint every heading in the theme. The five -on-* variants (-on-inverted, -on-contrast, -on-brand-primary, -on-brand-secondary, -on-brand-tertiary) are not bridged as Tailwind utilities — instead, each .surface--bg-* class reassigns --st-color-text-heading locally so headings flow through the surface cascade automatically. Components mounted outside that cascade can still consume the raw custom property via Tailwind’s arbitrary-value syntax, e.g. text-(color:--st-color-text-heading-on-inverted).

Action token families follow the pattern --st-color-action-{family}-{role} where family is one of primary, secondary, tertiary, neutral, inverted, on-primary, on-secondary, or on-tertiary, and role is one of bg, fg, border, bg-active, or focus. Representative examples: --st-color-action-tertiary-bg, --st-color-action-neutral-bg, --st-color-action-on-tertiary-bg. Outline button variants (outline, secondary-outline, tertiary-outline) reuse their matching family tokens with transparent backgrounds rather than introducing separate outline token families. The Button primitive consumes each variant family’s dedicated -focus token through its internal CSS-variable plumbing so focus rings stay aligned with the active action color.

Overlay and nav-surface tokens are wired to the primitives that own them: --st-color-nav-dropdown-bg / --st-color-nav-dropdown-border in Dropdown, --st-color-nav-mobile-bg / --st-color-nav-mobile-overlay in Drawer, and --st-color-overlay-scrim in global overlay surfaces such as search and lightbox backdrops.

Typography Tokens

Primitive families (--pt-font-family-sans, -serif, -mono, -display) define the font stacks. Semantic roles (--st-font-family-body, -heading, -ui, -prose, -code) map to primitives so a site can remap roles without changing components.

Primitive type scale (--pt-text-xs through --pt-text-5xl) defines fluid clamp() values pre-computed by the build-time token generator from min–max rem ranges and viewport bounds defined in src/core/config/fluid-tokens.config.ts. See Fluid Token Generation below.

Semantic heading scale (--st-font-size-h6 through --st-font-size-h1, plus -display and -code) maps heading roles to the primitive scale. Base element styles consume these semantic tokens; Heading.astro also supports explicit primitive size tokens via its size prop ("xs" through "5xl").

Font loading: Managed by the Astro Fonts API. Core provides defaults and the FontEntry type in src/core/config/fonts.config.ts; sites customize via src/site/config/fonts.config.ts. The Google Fonts provider downloads and self-hosts variable .woff2 files at build time. font-display: swap for all faces. System fallbacks in every stack. Metric-adjusted fallback faces generated automatically. Default shipped trio: Open Sans (sans), Source Serif 4 (serif), JetBrains Mono (mono).

Spacing Tokens

Primitive scale (--pt-space-0 through --pt-space-5xl) uses fluid clamp() values pre-computed by the build-time token generator. Components reference these directly: padding: var(--pt-space-md). See Fluid Token Generation below.

Semantic layout gaps (--st-layout-block-gap-sm through -xl) provide section-level spacing tokens that map to the primitive scale. These are the primary override point for adjusting vertical rhythm across a site.

Layout & Container Tokens

Content width constraints, container cap, gutter, sidebar dimensions, and breakpoints:

TokenValuePurpose
--pt-layout-content-max65chNarrow content (articles, docs)
--pt-layout-content-wide-max80chWider content (landing pages)
--pt-layout-container-min20remContainer minimum for fluid clamp interpolation
--pt-layout-container-max80remSite-wide container cap
--pt-layout-container-full100%Full-width layouts

Fluid Token Generation

Fluid spacing and typography tokens use clamp() values that scale linearly between a min and max viewport width. These values are pre-computed at build time by scripts/generate-fluid-tokens.ts rather than using runtime calc() division — Firefox Bug 1827404 prevents <length> / <length> in calc() with custom properties. (D-FT-01)

Source of truth: src/core/config/fluid-tokens.config.ts defines the viewport bounds and all token scales:

Config keyDefaultPurpose
vwMin20 (320px)Smallest viewport for fluid interpolation
vwMax90 (1440px)Largest viewport for fluid interpolation
spacing[]11 entries (3xs–5xl)Min/max rem pairs for each spacing step
typography[]9 entries (xs–5xl)Min/max rem pairs for each type step
custom[]1 entryArbitrary fluid tokens with explicit prop names

Site-level overrides go in src/site/config/fluid-tokens.config.ts — see Site Configuration for the override workflow.

Generated output: src/core/styles/fluid-tokens.css (core tokens) and src/site/styles/fluid-tokens.css (site override tokens, if any). Both files are committed but auto-generated — do not edit them directly.

Workflow: Edit the config → run npm run generate:tokens → commit the updated CSS. The validate pipeline includes generate:tokens:check which fails if generated CSS is out of sync with config.

The generated clamp() formula for each token:

--pt-space-md: clamp(MIN, interceptRem + slopeVw, MAX);

Where slope = (max − min) / (vwMax − vwMin) and intercept = min − slope × vwMin, fully resolved to numeric values at build time.

Breakpoint reference: CSS custom properties cannot be used in @media queries, so breakpoints are not tokenized. Use Tailwind’s responsive prefixes (sm:, md:, lg:, xl:, 2xl:) for responsive styles. The project uses Tailwind v4’s default breakpoints:

PrefixMin-widthTypical use
sm640pxLarge phones / landscape
md768pxTablets
lg1024pxLaptops / small desktops
xl1280pxDesktops
2xl1536pxLarge monitors

Z-Index Layer Tokens (REQ-00189)

Layered UI contexts (dropdowns, overlays, modals, sticky headers) use semantic --st-layer-* tokens rather than ad-hoc numeric z-index values. This follows the same three-layer pattern as other token categories: primitives define the numeric scale, semantic tokens express UI intent, and the Tailwind bridge exposes utilities for component markup.

Naming convention: Semantic layer tokens use the --st-layer-{role} pattern, where {role} describes the UI purpose — not a numeric rank. Bridge utilities follow the z-{role} pattern. Examples:

  • --st-layer-sticky → bridge utility z-sticky (sticky header, sidebar columns)
  • --st-layer-dropdown → bridge utility z-dropdown (menus, listbox panels)
  • --st-layer-modal-surface → bridge utility z-modal-surface (modal foreground surfaces)

Layer ordering model: Layers are ordered from lowest (page chrome like sticky headers) to highest (modal surfaces and toasts). The complete ordered inventory and its primitive mappings live in src/core/styles/semantic.css — that file is the authoritative source for the current layer set.

Backdrop/surface pairs: Overlay-style patterns that render both a backdrop and a foreground surface use explicit named layer pairs (e.g., z-modal-backdrop + z-modal-surface) rather than local arithmetic like calc(var(...) + 1). Each category that needs this pairing gets two tokens: *-backdrop and *-surface.

Consumption rule: Components use bridge utilities (z-dropdown, z-modal-surface, etc.) in markup. Direct var(--st-layer-*) references are allowed only in scoped <style> blocks where Tailwind cannot express the value cleanly. Direct primitive --pt-z-* references are not allowed in component code.

Site overrides: Sites can remap layer ordering by overriding the semantic tokens in src/site/styles/semantic.css. The primitive numeric scale remains in src/core/styles/primitives.css.

Component Sizing Tokens

Tokens for component-specific dimensions that don’t belong to a general scale:

  • --pt-size-nav-touch-target (2.75rem) — minimum touch target for nav items
  • --pt-size-nav-dropdown-min-width (8rem) — dropdown menu minimum width
  • --pt-size-logo-header-width — header logo display width (fluid custom token, see Fluid Token Generation)
  • --pt-size-logo-footer-width (12rem) — footer logo display width

Interaction Tokens

Duration (--pt-duration-fast, -normal, -slow), easing (--pt-ease-standard), and a --pt-motion-reduce-factor that drops to 0 under prefers-reduced-motion.

Theme Mode Switching

Light/dark mode uses a hybrid approach: data-theme attribute for persisted user choice, with prefers-color-scheme as initial fallback.

Selector convention:

  • :root, :root[data-theme="light"] — light mode tokens (default)
  • :root[data-theme="dark"] — dark mode tokens
  • JS resolves system preference at load time → sets data-theme

Only color semantic tokens (--st-color-*) differ between modes. Non-color tokens (spacing, typography, layout) are mode-independent and defined once in semantic.css.

File Organization

src/
├── core/
│   ├── config/
│   │   └── fluid-tokens.config.ts  # Fluid token definitions — source of truth (CORE-OWNED)
│   └── styles/
│       ├── global.css               # Entry point: @import order only (CORE-OWNED)
│       ├── fluid-tokens.css         # Generated fluid clamp() tokens — do not edit (CORE-OWNED)
│       ├── primitives.css           # --pt-* raw values (CORE-OWNED)
│       ├── semantic.css             # --st-* mode-independent (CORE-OWNED)
│       ├── light.css                # --st-color-* light mode (CORE-OWNED)
│       ├── dark.css                 # --st-color-* dark mode (CORE-OWNED)
│       ├── base.css                 # Element-level styles (CORE-OWNED)
│       ├── components.css           # Shared component classes (CORE-OWNED)
│       ├── starwind-bridge.css      # Optional Starwind component-system bridge (CORE-OWNED)
│       └── bridge.css               # Project @theme block (CORE-OWNED)
├── site/
│   ├── config/
│   │   └── fluid-tokens.config.ts   # Fluid token overrides (SITE-OWNED)
│   └── styles/
│       ├── fluid-tokens.css         # Generated site override tokens — do not edit (SITE-OWNED)
│       ├── primitives.css           # --pt-* site overrides (SITE-OWNED)
│       ├── semantic.css             # --st-* site overrides (SITE-OWNED)
│       ├── light.css                # --st-color-* light overrides (SITE-OWNED)
│       ├── dark.css                 # --st-color-* dark overrides (SITE-OWNED)
│       ├── base.css                 # Element-level overrides (SITE-OWNED)
│       ├── components.css           # Shared component class overrides (SITE-OWNED)
│       ├── starwind-bridge.css      # Optional Starwind bridge activation/overrides (SITE-OWNED)
│       └── bridge.css               # Project @theme overrides and extensions (SITE-OWNED)
└── scripts/
    └── generate-fluid-tokens.ts     # Build-time token generator (CORE-OWNED)

Override precedence: core fluid-tokens → core primitives → core semantic → core light/dark → site fluid-tokens → site primitives → site semantic → site light/dark → core base → site base → core components → site components → site Starwind bridge (optional activation surface) → core bridge → site bridge. Site files override core by CSS cascade where paired layers load core first and site second. The Starwind bridge remains opt-in: src/site/styles/starwind-bridge.css loads by default as an empty surface, and sites import src/core/styles/starwind-bridge.css there only when they use Starwind components. Fluid token files are generated — see Fluid Token Generation.

Customizing a Site’s Tokens

Site owners override tokens in src/site/styles/. The pattern is the same as CSS custom property overrides — redeclare the token with a new value:

/* src/site/styles/primitives.css (SITE-OWNED) */
:root {
  --pt-color-brand-primary: oklch(0.55 0.2 250);
  --pt-color-brand-primary-emphasis: oklch(0.45 0.22 250);
  --pt-color-brand-primary-subtle: oklch(0.75 0.15 250);
}

Only override what differs. Unset tokens inherit the core defaults.

Lint Rules

The lint:tokens validation step enforces these rules:

Color enforcement (strict):

  1. No raw color literals (#hex, rgb(), hsl(), oklch()) in component/layout files.
  2. No --pt-color-* references in component/layout files. Use --st-color-* or Tailwind color utilities.

Non-color primitives (allowed in components):

  • --pt-space-*, --pt-text-*, etc. may be referenced directly. These are not lint errors.

Structural rules: 3. No --st-* or --pt-* definitions outside src/core/styles/ and src/site/styles/. 4. Every --st-color-* in light.css must also appear in dark.css (and vice versa). 5. Every token intended for Tailwind consumption must appear in the @theme bridge block.

REQ-00012 normative Theme switching shall be supported at runtime.
REQ-00014 implemented Theme switching shall be implemented via document-level class or attribute changes.
REQ-00023 implemented The theme shall use a semantic design token system.
REQ-00024 implemented The theme shall implement a CSS variable-driven token system.
REQ-00025 normative Color definitions shall use OKLCH where practical.
REQ-00027 normative Theme tokens shall maintain WCAG 2.2 AA contrast conformance.
REQ-00029 implemented The token system shall support Primary, Secondary, and Tertiary brand color roles.
REQ-00030 normative The Tertiary brand color shall be restricted to decorative/emphasis usage.
REQ-00031 implemented The theme shall define an allowed semantic color palette.
REQ-00032 implemented Brand color roles shall include variants for emphasis and subtle states.
REQ-00033 implemented The theme shall define radii presets.
REQ-00034 implemented The theme shall define shadow presets.
REQ-00035 normative Components shall use presets rather than ad-hoc values.
REQ-00036 normative Styling decisions shall resolve from tokens or presets.
REQ-00037 normative Utility class usage shall not bypass semantic token constraints.
REQ-00038 normative Interactive states shall resolve from semantic tokens.
REQ-00040 normative Components shall resolve styling from semantic design tokens rather than raw color values.
REQ-00042 normative Dark mode and all mode differences shall be expressed through semantic token value swapping; component-level per-mode styling overrides shall be exceptional and explicitly documented.
REQ-00134 implemented Local font loading shall be supported.
REQ-00135 normative Font loading strategies shall minimize layout shift.
REQ-00136 normative Third-party font CDNs shall not be assumed.
REQ-00151 normative Tailwind utility token mappings shall resolve through documented semantic design-token aliases (for example via `@theme` / `@theme inline`) rather than raw palette literals in component/layout code.
REQ-00153 implemented Tailwind utility token mappings shall include documented semantic aliases for link, status, form, and icon roles so component/layout code does not rely on raw palette literals.
REQ-00188 implemented The spacing scale shall be finite and token-driven. Components and layouts shall avoid hard-coded spacing values outside the defined scale.
REQ-00189 implemented Semantic z-index tokens shall be defined for layered UI contexts including dropdown, sticky, overlay, modal, and toast layers. Components shall resolve stacking from these tokens rather than ad-hoc z-index values.
REQ-00190 implemented The theme shall define standardized layout width constraints. At minimum a content width, a wide width, and a prose/reading width shall be defined as canonical layout tokens.
REQ-00192 implemented The theme shall define standardized vertical spacing rules between content blocks and around headings, lists, and tables within long-form content.
REQ-00250 implemented Typography shall use a fluid type scale based on CSS clamp() functions, generated from a declarative token configuration file, ensuring smooth scaling between defined viewport breakpoints without requiring media query breakpoints.
REQ-00256 implemented Fluid design tokens (typography, spacing) shall be generated from a declarative token configuration file via a build script (generate:tokens), with drift detection (generate:tokens:check) integrated into the validate pipeline to ensure generated CSS output stays synchronized with configuration source.

Search

Search across pages and articles. Use arrow keys to navigate results.

Search across pages and articles.

Loading search...

Search is unavailable. Please try again later.

    No results for ""

    Try different keywords or fewer words.