Design Tokens

July 11, 2026 · View on GitHub

The complete catalog of design tokens declared in src/styles/globals.css. Every color, radius, shadow, font, font size, spacing value, and z-index used by the admin / editor / UI primitives is here. CSS Modules in src/admin/, src/admin/pages/site/, and src/ui/ MUST reference these via var(--*) — hardcoded hex / rgb / hsl is gated by css-token-policy.test.ts, hardcoded font-size pixels are gated by admin-typography-token-policy.test.ts, and hardcoded margin / padding / gap pixels are gated by admin-spacing-token-policy.test.ts.


TL;DR

  • One file: src/styles/globals.css.
  • Grouped by role: fonts/fluid type, fluid spacing, core surfaces/text/borders, overlays/scrims, identity accents, semantic state, radii, shadows, canvas, syntax, and z-index.
  • Two-layer color model: achromatic base + semantic / categorical color layer on top. See docs/design.md for the rationale.
  • Add a new token by editing globals.css. Reference it with var(--your-token) in CSS Modules.
  • The src/modules/ directory is exempt from the no-hardcoded-color rule — module CSS ships to published pages where admin tokens are not guaranteed to exist.

Fonts

--font-sans: "Inter Variable", system-ui, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;

Token names: --font-sans, --font-mono.

Inter Variable is loaded via a targeted @font-face declaration in globals.css — Latin subset only (unicode-range: U+0000-00FF, …), with font-display: swap. Non-Latin characters fall back to system-ui. The monospace stack is entirely system-native; no custom monospace font is loaded.

If the admin UI needs broader character coverage, add a second @font-face block in globals.css with the appropriate unicode-range pointing at the matching subset file under @fontsource-variable/inter/files/.

Fluid type scale

Admin UI font sizes use the Core Framework-style --text-* scale directly. These tokens are fluid across viewport width but intentionally narrow for dense admin chrome. Use the nearest semantic step instead of introducing one-off pixel sizes.

Settings → Preferences can remap the scale through data-editor-text-scale on the document and layout roots. The small, large, and extra-large options shift the same token names down or up the base scale, so component CSS continues to say font-size: var(--text-s) rather than branching per preference.

Each visible token has a stable base token used by those preference remaps: --text-3xs-base, --text-2xs-base, --text-xs-base, --text-s-base, --text-m-base, --text-l-base, --text-xl-base, --text-2xl-base, --text-3xl-base, --text-4xl-base, --text-5xl-base, --text-6xl-base, --text-7xl-base.

TokenFluid rangeTypical use
--text-3xs8px → 9pxTiny badges, axis labels, micro metadata
--text-2xs9px → 10pxDense metadata, compact chips
--text-xs10px → 11pxPanel labels, toolbar chrome, descriptions
--text-s11px → 12pxDefault compact body text, inputs, buttons
--text-m12px → 13pxCard/body copy and readable secondary text
--text-l13px → 14pxSection titles and prominent labels
--text-xl14px → 16pxDialog titles and compact page titles
--text-2xl16px → 18pxPage headings
--text-3xl18px → 20pxLarge page headings
--text-4xl20px → 24pxHero numbers / large empty states
--text-5xl24px → 28pxDisplay headings
--text-6xl32px → 40pxKPI values
--text-7xl40px → 56pxLargest admin display values

These are admin tokens. The published-site Framework engine also emits short names such as --text-s; those belong to the generated site CSS, not admin chrome. Editor chrome injected into the canvas iframe maps admin sizes to --chrome-text-* before using them so it cannot override the site's Framework typography.


Fluid spacing scale

Admin UI spacing uses the Core Framework-style --space-* scale directly. Use these tokens for CSS-authored margin, padding, gap, row-gap, column-gap, and SVG width/height values in admin and shared UI CSS modules. Keep true hairlines on --space-px.

TokenFluid rangeTypical use
--space-px1px fixedHairline gaps and exact 1px spacing
--space-4xs2px → 3pxMicro offsets, dense icon nudges
--space-3xs3px → 4pxVery tight inline padding
--space-2xs4px → 5pxTight chip gaps
--space-xs5px → 6pxCompact control gaps
--space-s6px → 8pxDefault dense row gaps
--space-m8px → 10pxDefault panel/control spacing
--space-l10px → 12pxSection row padding
--space-xl12px → 14pxDialog/control interior padding
--space-2xl14px → 16pxLarger panel gaps
--space-3xl16px → 18pxIcon-size SVGs and roomy control gaps
--space-4xl18px → 20pxCompact empty-state spacing
--space-5xl20px → 24pxCard and modal spacing
--space-6xl24px → 28pxLarge controls / icon tiles
--space-7xl28px → 32pxPage-section rhythm
--space-8xl32px → 40pxLarge page body spacing
--space-9xl40px → 48pxProminent empty-state spacing
--space-10xl48px → 56pxLarge dashboard/display spacing
--space-11xl56px → 72pxLarge SVG previews and major vertical gaps
--space-12xl84px → 108pxLargest admin display spacing

These are admin tokens. The published-site Framework engine also emits short names such as --space-s; those belong to the generated site CSS, not admin chrome. Editor chrome injected into the canvas iframe maps admin spacing to --chrome-space-* before using it so it cannot override the site's Framework spacing.


Surface hierarchy (achromatic base)

Six surface tones for depth. Lighter is higher in the stack.

The default values below are the dark theme. data-editor-theme="light" on the document or layout root overrides the same token names in globals.css, so component CSS stays theme-agnostic.

TokenHexUse
--bg-body#000000Page bottom — root, behind everything
--bg-surface#1b1b1bDarker parent of tile cards / sidebar fill
--bg-surface-2#282828Tile cards themselves, panel bodies
--bg-surface-3#323232Tile/card hover, nested structural surfaces
--bg-surface-4#4a4a4aStronger nested structural surface
--bg-surface-5#605f5fStrongest nested structural surface

Tile/card hovers change tone, not border. Compact interactive chrome — buttons, segmented controls, toolbar chips, selected rows, and pressed states — uses --overlay-5/10/20/30 for state fills so light and dark themes keep the same visible hierarchy.


Borders

TokenHexUse
--border#333333Default 1px borders on input controls, dividers
--border-muted#2e2e2eSlightly elevated borders (active states on bordered controls)
--border-subtle#1f1f1fQuiet dividers and low-emphasis panel edges
--border-strong#4a4a4aStronger structural borders and high-emphasis separators

Text hierarchy

Five tones, each a different meaning level.

TokenHexMeans
--text-bright#f4f4f5Titles, headings, KPIs
--text#edededPrimary body text
--text-muted#a1a1aaLabels, secondary UI
--text-subtle#787878Muted / placeholder
--text-disabled#52525bDisabled / very subtle

Add a new text tone only by adding a new token here.


Scrollbar chrome

Scrollbars are admin chrome, not identity. Keep them achromatic so the panel rail remains the dominant colored vertical affordance.

TokenValue / sourceUse
--scrollbar-size6pxWebKit/Blink scrollbar width and height
--scrollbar-radiusvar(--radius-sm)Thumb radius
--scrollbar-tracktransparentTrack and corner background
--scrollbar-thumbcolor-mix(in srgb, var(--text-subtle) 58%, transparent)Default thumb color, also Firefox thumb color
--scrollbar-thumb-hovercolor-mix(in srgb, var(--text-muted) 72%, transparent)Hover thumb color

globals.css applies these through scrollbar-color for Firefox and ::-webkit-scrollbar for WebKit/Blink. Scrollable panel layouts that place rail navigation beside content should use scrollbar-gutter: stable.


Overlays and scrims

Foreground overlays are used for selected rows, pressed states, borders, compact control tracks, and subtle surface lifts. The dark theme uses white overlays; the light theme swaps the same token names to dark overlays. Black scrims are used for shadows and modal backdrops. Suffixes are alpha x 100.

Overlay tokens: --overlay, --overlay-5, --overlay-10, --overlay-20, --overlay-30, --overlay-40, --overlay-50, --overlay-60, --overlay-70, --overlay-80, --overlay-90.

Scrim tokens: --scrim, --scrim-10, --scrim-20, --scrim-30, --scrim-40, --scrim-50, --scrim-60, --scrim-70, --scrim-80, --scrim-90.


Identity accents

Token-backed identity colors for widget categories, panel rails, sidebar icons, tag pills, and storage breakdown segments. Panel rails assign these automatically from the full panel identity with repeat avoidance inside the visible rail group.

TokenHexConventional category
--accent-1#8ee6c8"Saved / system / status"
--accent-2#c8b6ff"Pages / structure"
--accent-3#9bdcff"Storage / data / configuration"
--accent-4#ffc7a8"Posts / media / activity"
--accent-5#ffb6cdSecondary warm identity tint
--accent-6#b8f28bSecondary green identity tint
--accent-7#f7df72Secondary yellow identity tint
--accent-8#83e7ffSecondary blue identity tint
--accent-9#f0a6ffSecondary violet identity tint
--accent-10#ff9f9fSecondary red identity tint

Each accent also has a standard 10% tint for soft backgrounds: --accent-1-10, --accent-2-10, --accent-3-10, --accent-4-10, --accent-5-10, --accent-6-10, --accent-7-10, --accent-8-10, --accent-9-10, --accent-10-10. Used by Widget (tint prop), the rail accent helper (src/ui/railAccent.ts), TagPill, and the storage breakdown chart. The light theme overrides the same accent token names with darker foreground-safe variants, so shared rail icons and badge text stay readable on white surfaces. Adding another identity color requires a new token — don't inline a color.


Tag pill accents

TagPill maps the first meaningful alphanumeric character of its label to a stable numbered accent. This keeps selector punctuation from driving the color while giving class names, HTML tags, and badges enough visual variety without creating a second tag-specific tint scale.


Semantic state (meaning layer)

Danger / error

TokenValueUse
--danger#ef4444Error fill (button, badge, border)
--danger-light#f87171Lighter danger for text
--danger-lighter#fca5a5Very light danger tint
--danger-text#fecacaDanger badge text
--danger-5rgba(239, 68, 68, 0.05)Very quiet danger wash
--danger-10rgba(239, 68, 68, 0.1)Danger pill / message surface
--danger-20rgba(239, 68, 68, 0.2)Danger surface border
--danger-30rgba(239, 68, 68, 0.3)Stronger danger border / fill
--danger-40rgba(239, 68, 68, 0.4)Highest danger alpha fill

Warning

TokenValueUse
--warning#f59e0bWarning fill
--warning-text#fde68aWarning badge text
--warning-10rgba(245, 158, 11, 0.1)Warning surface
--warning-20rgba(245, 158, 11, 0.2)Warning border / stronger wash
--warning-30rgba(245, 158, 11, 0.3)Warning surface border

Success

TokenValueUse
--success#34d399Saved / OK indicator
--success-bright#4ade80Agent tool success
--success-text#d1fae5Success badge text
--success-text-muted#a7f3d0Success hint text
--success-10rgba(52, 211, 153, 0.1)Success pill surface
--success-20rgba(52, 211, 153, 0.2)Success wash / match highlight
--success-30rgba(52, 211, 153, 0.3)Success pill / alert border
--success-40rgba(52, 211, 153, 0.4)Highest success alpha fill

Info

TokenValueUse
--info-text#c4b5fdViolet-tinted info text

Canvas (selection / hover affordances)

TokenUse
--canvas-chrome-shadowAchromatic drop shadow for canvas chrome overlays
--canvas-selection-ringNeon green inset ring for the selected node
--canvas-hover-ringNeon pink inset ring for the hovered node
--canvas-selector-ringNeon orange inset ring for selector-panel match sweeps
--canvas-selection-ring-colorBare selection colour for outline / border / custom shadows
--canvas-hover-ring-colorBare hover colour for outline / border / custom shadows
--canvas-selector-ring-colorBare selector-affinity colour for custom shadows
--tree-drop-lineDrag-and-drop insertion line in tree views
--tree-drop-line-shadowGlow behind the tree insertion line
--tree-drop-inside-bgValid drop-inside row background
--tree-drop-inside-borderValid drop-inside row border
--tree-drop-invalid-bgInvalid drop row background
--tree-drop-invalid-borderInvalid drop row border
--canvas-placeholder-bgShared diagonal-stripe empty-module placeholder background
--canvas-live-scrimLive-preview overlay scrim, theme-adjusted

Bare colour variants (*-ring-color) are for surfaces that need an outline / border-color / custom shadow geometry rather than the full inset-ring shorthand. Keep them in sync with the shorthands. The selector ring (#ff8800) is a third distinct identity so a "show me everything this selector touches" sweep reads differently from selection (green) and hover (pink).

Canvas placeholder

--canvas-placeholder-bg:
    repeating-linear-gradient(
        -45deg,
        color-mix(in srgb, var(--text-subtle) 6%, transparent) 0 6px,
        transparent 6px 12px
    ),
    color-mix(in srgb, var(--text-subtle) 4%, transparent);

The shared diagonal-stripe pattern used by every module's "empty" placeholder (image, video, container, slot-outlet, VC ref placeholders). Strictly achromatic. Edit once, retune everywhere.


Code editor syntax (GitHub Dark inspired)

Used inside CodeMirror only. Don't reach for these in editor chrome.

TokenHexUse
--syntax-keyword#ff7b72Keywords (if, return)
--syntax-entity#d2a8ffFunction names, types
--syntax-property#7ee787Object properties
--syntax-variable#ffa657Variables
--syntax-string#a5d6ffString literals
--syntax-constant#79c0ffNumbers, constants
--syntax-comment#8b949eComments
--syntax-operator#c9d1d9Operators, punctuation
--syntax-invalid#ffa198Invalid syntax

Border radius

TokenValueUse
--radius-sm3pxTight chips, micro-badges, segmented-control inner indicator
--radius6pxDefault editor controls, toolbar buttons, ghost menu items
--panel-radius12pxFloating overlay panels
--card-radius16pxBorderless tile cards (Widget, dashboard cells, module inserter tiles)
--input-radius1emPill-shaped inputs, class / property chips
--tooltip-radius6pxTooltips

Shadows

TokenUse
--focus-ring0 0 0 1px var(--overlay-20) — 1px achromatic focus ring
--panel-blurBackdrop blur amount for floating overlay panels
--shadow-panelComposite for floating panels: bottom-inset shadow + drop shadow
--shadow-panel-inset-bottomSub-token: inset 0 -1px 0 var(--scrim-40)
--shadow-panel-dropSub-token: 0 12px 40px rgba(0, 0, 0, 0.65)
--shadow-input-focusInset composite achromatic glow for focused inputs
--shadow-tooltip0 4px 16px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.04)

Use --shadow-panel directly when you need a floating-panel feel. Don't recompose from the sub-tokens.


Floating overlay panels

background: var(--bg-surface);
border: 1px solid var(--overlay-10);
border-radius: var(--panel-radius);
backdrop-filter: blur(var(--panel-blur));
box-shadow: var(--shadow-panel);

Used by spotlight, popovers, modals — anything that floats above the editor with a blur backdrop.


Inline code surface

background: var(--scrim-30);

Used by the Code primitive for inline code chip backgrounds. Don't reuse for block-level code editors (that's CodeMirror, which uses the --syntax-* tokens).


Keycap (Kbd primitive)

A sculpted, top-lit dark mechanical keycap. The face is a vertical gradient; the box-shadow stack fakes a raised cap — top highlight, concave inner shadow, a solid bottom edge, and a soft drop shadow. All values are achromatic, sitting inside the editor surface ramp.

TokenValueUse
--kbd-face-top#434343Top of the face gradient (between surface-3 and -4)
--kbd-face-bottom#2f2f2fBottom of the face gradient (just below surface-3)
--kbd-face-top-hover#4d4d4dHover face gradient top (one step brighter)
--kbd-face-bottom-hover#363636Hover face gradient bottom
--kbd-bordervar(--border)Keycap border — achromatic, same as editor controls
--kbd-textvar(--text-muted)Key label text — same register as surrounding labels
--kbd-highlightrgba(255, 255, 255, 0.06)Top inner highlight (inset box-shadow)
--kbd-inner-shadowrgba(0, 0, 0, 0.22)Soft concave bottom inner shadow
--kbd-edgergba(0, 0, 0, 0.30)1px bottom edge — the key's simulated thickness
--kbd-droprgba(0, 0, 0, 0.28)Small drop shadow beneath the cap

These tokens are scoped to Kbd and ShortcutKeys. Don't reach for them in other components.


Inputs

background: transparent;
border: 1px solid var(--overlay-20);
border-radius: var(--input-radius);
color: var(--text);
.field:hover { border-color: var(--overlay-30); }
.field:focus { border-color: var(--overlay-50); box-shadow: var(--shadow-input-focus); }

Cards are filled and borderless; inputs are unfilled and bordered. That's the load-bearing visual distinction between containers and controls.


Z-index layers

Four global tokens cover the layered surfaces that float above the editor:

--z-dropdown:           20;
--spotlight-z-index:  9000;
--toast-z-index:     10000;
--tooltip-z-index:   10001;

Token names: --z-dropdown, --spotlight-z-index, --toast-z-index, --tooltip-z-index.

--tooltip-z-index is deliberately the highest token so tooltips are never occluded by the surface their trigger lives on. --toast-z-index sits above modal layers and below tooltips. --spotlight-z-index is reused by several modal-level surfaces that need to sit above the editor chrome.

Global modal layer (all raw values in the shared admin stacking context):

ValueWhat occupies it
9000Spotlight backdrop (--spotlight-z-index); Settings modal backdrop; ModuleInserterDialog backdrop
9001Settings dialog wrapper (--spotlight-z-index + 1)
9050MediaPickerModal backdrop (calc(--spotlight-z-index + 50)) — sits above Settings because the picker can be opened from inside Settings (e.g. Settings → General → Favicon → Browse library…)
10000BodySlashMenu — predates tokenisation; see inline comment in BodySlashMenu.module.css
10001Tooltips (--tooltip-z-index); AdminContextMenuGuard

The gap between 9001 (Settings dialog) and 9050 (MediaPickerModal) is intentional headroom for any future sub-dialogs inside Settings. The gap between 9050 and 10000 (BodySlashMenu) keeps the slash menu above all modal layers. Do not add new raw values into these ranges without updating this table.

The visual editor uses additional raw z-index values that are not tokenised. They fall into two independent stacking contexts:

Editor layout context (shared stacking context for the editor chrome):

ValueWhat occupies it
0CanvasRoot — an isolation root; all canvas-internal values are confined here
30Main toolbar
50Floating panels: PropertiesPanel, AgentPanel, DomPanel
55LeftSidebar, RightSidebar, PanelRail
90Shared admin floating windows (FloatingWindow, MediaViewerWindow, agent image preview)
80CodeEditorPanel
201Toolbar popovers / dropdowns
400–401PreviewOverlay

Canvas-internal context (confined inside CanvasRoot's z-index: 0):

ValueWhat occupies it
50PluginCanvasOverlayLayer
51Selection ring, hover ring, selection toolbar
53CanvasModeToggle, CanvasNotch
60CanvasContextSelector
2147483647Drop-indicator layer inside iframe (must beat arbitrary module stacking contexts)

CanvasRoot declares z-index: 0; position: relative to establish the isolation. Without it, the canvas-internal z-index 51 would escape into the layout context and paint over floating panels at z-index 50. See docs/editor.md → "Canvas stacking context isolation" for the full explanation.

Raw canvas-internal values are intentional exceptions — they cannot be tokens because they are relative to an isolated stacking context, not the global one. Do not add new raw z-index values outside this established ladder.


Tooltips

Tooltip chrome uses --bg-body, --text-bright, --overlay-5, --shadow-tooltip, --tooltip-radius, and --tooltip-z-index directly.


Spotlight (Cmd+K command palette)

Spotlight has only layout/z-index tokens of its own. Its colors come from the same global overlay, scrim, text, and semantic state tokens as the rest of the admin UI.

TokenValue
--scrim-50Backdrop fill
--backdrop-blur8px
--spotlight-z-index9000
--spotlight-width640px
--overlay-10Selected row and skeleton shimmer
--success-20Match highlight background
--danger-light / --danger-10Destructive command states
--progress-shimmerTravelling highlight on progress bars

Bespoke surface tints

These tokens are narrow, named exceptions for authored surfaces that need a specific translucent treatment and cannot be expressed as a reusable semantic state.

TokenUse
--plugins-hero-tint-aFirst plugins workspace hero background tint
--plugins-hero-tint-bSecond plugins workspace hero background tint
--canvas-live-scrimTheme-adjusted live-preview overlay scrim

Charts

Default chart tint, series colors, glow halos, empty-segment styling. Used by dashboard widgets and the framework scale UI.

TokenValueUse
--chart-default-tintvar(--accent-4)Fallback when no per-call tint
--chart-series-min#38bdf8 (sky 400)"Mobile" series
--chart-series-min-glowrgba(56, 189, 248, 0.40)Halo for active mobile segment
--chart-series-max#4ade80 (green 400)"Desktop" series
--chart-series-max-glowrgba(74, 222, 128, 0.40)Halo for active desktop segment
--chart-segment-emptyvar(--overlay-5)Empty segment fill
--chart-segment-empty-bordervar(--overlay-5)Empty segment border
--chart-track-bgvar(--overlay-5)Track background behind chart segments

Reduced motion

globals.css includes a non-negotiable reduced-motion override:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

The two !important declarations here are the only legitimate !important usages in globals.css. Constraint #189.


Adding a new token

  1. Pick the right group in src/styles/globals.css (color → semantic group; radius → radius group; z-index → z-index group).
  2. Add the custom property with a one-line comment if the meaning isn't obvious from the name.
  3. Reference it via var(--*) in CSS Modules.
  4. Add a row to the matching table in this doc.
  5. If it introduces a new concept (not a variation of an existing group), add a paragraph explaining what it's for in this doc and in docs/design.md.

Forbidden patterns

PatternUse instead
color: #ededed; in an admin / ui CSS modulecolor: var(--text);
border: 1px solid #333;border: 1px solid var(--border);
box-shadow: 0 4px 16px rgba(0,0,0,0.5);box-shadow: var(--shadow-panel-drop);
Inventing a one-off radiusUse the radius scale
Inventing a one-off z-indexUse the existing z-index tokens
Reaching for a one-off rail colorUse railAccent / assignRailAccents, or add a token
Hardcoding the canvas selection ring colorvar(--canvas-selection-ring)

  • docs/design.md — design principles + surface systems + UI primitives
  • docs/reference/ui-primitives.md — which primitive uses which tokens
  • Source-of-truth file: src/styles/globals.css
  • Gate tests:
    • src/__tests__/architecture/css-token-policy.test.ts
    • src/__tests__/architecture/css-token-vocabulary.test.ts
    • src/__tests__/architecture/admin-typography-token-policy.test.ts
    • src/__tests__/architecture/admin-spacing-token-policy.test.ts
    • src/__tests__/architecture/noTailwindUtilities.test.ts
    • src/__tests__/architecture/no-css-var-fallbacks.test.ts
    • src/__tests__/architecture/scrollbar-chrome.test.ts — scrollbar tokens declared; both Firefox and WebKit/Blink styled; properties panel uses scrollbar-gutter: stable