Foundations
The non-negotiable base layer — color, type, spacing and borders. Motion, iconography and overlays live on their own linked pages. Everything else in the system is built on these tokens, and every swatch and ramp below renders from the registry — the same data that generates the stylesheet and feeds consumer apps.
Color system
A grayscale semantic foundation. Roles communicate hierarchy and state; client identity is supplied by a versioned recipe rather than embedded product color.
Grayscale identity foundation
Locked as the neutral contract. Every core role is grayscale so the system can prove hierarchy, contrast, focus, status, and interaction without relying on a client's brand. A consumer recipe may remap semantic color roles, but it cannot bypass the token contract or alter component structure.
Ink & neutral surfaces
On-dark — the header’s inverted neutrals
The ink-900 header and nav are the one place the UI sits on near-black, so the usual neutrals invert. Tokenised so chrome on dark is never inline-styled with raw hex — this very app’s left nav uses exactly these tokens for its labels, hover and selection states.
Status — color + always a word
Four states cover the whole tool. Status is never color-only: it always ships with a label and (in chips) a dot, so it survives colorblindness and grayscale printing.
Typography
The native system font (San Francisco · Segoe · Roboto) for everything readable; the matching system monospace (SF Mono · Consolas) for IDs, labels, timestamps, and technical metadata. Headings run heavy (800) with a deliberate size jump to body — but body and labels never drop below 400. Mono signals “machine fact, secondary”. No web fonts load — the UI feels instant and native.
Real headings vs styled labels
- Use one real
<h1>per route (the page title) and<h2>for genuine sections. - Card titles are
<h3>only when the card is a true content section; in dense lists, render them as styled text to avoid a heading-soup outline. - Row labels, chips, and timestamps are never headings — they’re mono labels.
Reducing heading clutter
- In a location card, the name is the only “title.” Address, ID, status are labels and metadata — not sub-headings.
- Prefer a mono caps label + value pair over a bold sub-heading for every field.
- Cap visible weight: at most one 600 element per card. If everything is bold, nothing is.
- Secondary captions, descriptions & metric labels use
.ss-subtext— a named role, so secondary text is never hand-styled per surface.
Spacing & radius
A 4px base scale with two micro steps. Lists live at the tight end (8–12px); detail and intake breathe at 16–24px. Consistency matters more than the exact number.
xschips, dots, platform tiles.smbuttons, inputs.mdcards, panels (the default container).lglarge shells, dialogs, the intake column.
Density targets. Location list rows: 12px vertical padding, 16px gutters. Card internal gap: 12px. Detail field rows: 12px vertical. Page gutters: 24–32px. A location card should comfortably show name, address, ID, four platforms, and status without scrolling inside the card.
Borders & elevation
Borders first, shadows almost never. A flat, bordered UI scans better at density and prints cleanly. Shadow is reserved for things that float above the page.
--ss-border-width hairline in --ss-color-border, no shadow. This is 95% of the UI.--ss-shadow-pop.Rules
- Static cards & rows: border only. Never a resting shadow.
- Hover on interactive cards: border darkens to
--ss-color-border-strong+ optional--ss-shadow-sm. - Overlays (dropdowns, dialogs, toasts):
--ss-shadow-pop, because they genuinely float. - Separation inside a panel: a hairline divider, not a gap-and-shadow.
Elevation tokens
Use sparingly — borders first. Shadows are reserved for things that float.
| Token | Value | Use |
|---|---|---|
--ss-shadow-sm | 0 1px 2px rgba(24,24,24,0.05) | Resting card |
--ss-shadow-md | 0 4px 12px rgba(24,24,24,0.10), 0 1px 3px rgba(24,24,24,0.06) | Hover lift |
--ss-shadow-pop | 0 8px 28px rgba(24,24,24,0.16) | Floating overlays |
--ss-shadow-input-inset | rgba(24,24,24,0.04) | Hairline inset shadow on input edges |
Glass material
The premium translucent surface for things that FLOAT over the page — dialogs, menus, popovers. The page behind stays sharp; only a floating surface frosts what is directly beneath it. The input and inset values were promoted to tokens in V2 — they were previously hardcoded inside the dialog stylesheet.
The premium translucent surface for things that FLOAT over the page: dialogs, menus, popovers. The page behind stays sharp; only a floating surface frosts what is directly beneath it.
| Token | Value | Use |
|---|---|---|
--ss-glass-bg | rgba(252,252,252,0.78) | Balanced — glassy yet soft over busy content |
--ss-glass-bg-solid | rgba(252,252,252,0.92) | Denser, for tall / legible bodies |
--ss-glass-blur | 50px | Backdrop blur radius |
--ss-glass-saturate | 135% | Backdrop saturation boost |
--ss-glass-border | rgba(255,255,255,0.6) | Bright glass edge |
--ss-glass-hi | rgba(255,255,255,0.6) | Inset top highlight |
--ss-glass-shadow | 0 1px 0 var(--ss-glass-hi) inset, 0 16px 44px rgba(24,24,24,0.16), 0 2px 8px rgba(24,24,24,0.06) | Composite glass shadow — highlight + deep soft drop |
--ss-glass-input-bg | rgba(255,255,255,0.62) | Frosted input resting on glass |
--ss-glass-input-bg-focus | rgba(255,255,255,0.92) | Frosted input focused — denser |
--ss-glass-input-line | rgba(24,24,24,0.12) | Hairline input edge on glass |
--ss-glass-inset-line | rgba(24,24,24,0.07) | Hairline divider inside glass |
--ss-color-shimmer-high | rgba(255,255,255,0.18) | Skeleton/meter sheen highlight (lower opacity) |
--ss-color-shimmer-mid | rgba(255,255,255,0.45) | Skeleton/meter sheen mid-band highlight (higher opacity) |
Structure tokens
V2 finished the token layer: stroke weights, control heights, stacking order and the focus ring are decisions, not magic numbers. All were promoted from previously-hardcoded values.
Border widths
Stroke weights are a decision, not a magic number.
| Token | Value | Use |
|---|---|---|
--ss-border-width | 1px | Standard stroke — borders, dividers, outlines |
--ss-border-width-hairline | 0.5px | Hairline on glass surfaces |
--ss-border-width-focus | 2px | Focus ring line weight |
Control sizes
The few fixed heights everything aligns to — header, controls, tiles.
| Token | Value | Use |
|---|---|---|
--ss-size-control | 34px | Standard control height — buttons, inputs, facets |
--ss-size-control-small | 30px | Small control height |
--ss-size-tile | 26px | Compact choice indicator and mark size |
--ss-size-touch-target | 44px | Minimum square touch target |
--ss-size-dialog-sm | 400px | Small dialog inline maximum |
--ss-size-dialog-md | 520px | Medium dialog inline maximum |
--ss-size-dialog-lg | 680px | Large dialog inline maximum |
--ss-element-button-icon-padding | 11px | Icon-button inline inset |
--ss-element-button-icon-gap | 7px | Icon-button internal gap |
--ss-element-chip-height | 22px | Status chip minimum block size |
--ss-element-status-indicator-size | 6px | Status and meter indicator thickness |
--ss-element-dialog-icon-size | 36px | Dialog icon tile |
--ss-element-dialog-close-offset-block | -3px | Dialog close optical block offset |
--ss-element-dialog-close-offset-inline | -6px | Dialog close optical inline offset |
--ss-element-control-padding-inline | 10px | Native control inline inset |
--ss-element-control-icon-offset | 11px | Native control trailing icon offset |
--ss-element-segment-inset | 3px | Segmented-control thumb inset |
--ss-element-segment-option-padding | 14px | Segmented-control option inline inset |
--ss-element-dropdown-menu-offset | -6px | Dropdown menu closed-state block offset |
--ss-element-textarea-min-height | 88px | Textarea default minimum block size |
--ss-element-drawer-width | 360px | Drawer preferred inline size |
--ss-element-check-glyph-size | 22px | Completion check glyph |
--ss-element-text-gap | 5px | Tight helper-text separation |
--ss-element-halo-spread | 14px | Completion halo maximum spread |
--ss-size-layout-grid-sm | 12rem | Small intrinsic grid item minimum |
--ss-size-layout-grid-md | 16rem | Medium intrinsic grid item minimum |
--ss-size-layout-grid-lg | 22rem | Large intrinsic grid item minimum |
--ss-size-layout-content | 70rem | Readable centered content maximum |
--ss-size-layout-wide | 90rem | Wide centered content maximum |
--ss-size-layout-full | 100% | Uncapped centered content width |
--ss-size-layout-sidebar | 18rem | Intrinsic sidebar preferred width |
--ss-size-layout-sidebar-threshold | 50% | Content threshold before sidebar wraps |
--ss-size-layout-switcher-sm | 24rem | Small switcher stack threshold |
--ss-size-layout-switcher-md | 36rem | Medium switcher stack threshold |
--ss-size-layout-switcher-lg | 48rem | Large switcher stack threshold |
Z-index
Stacking order is a named scale, not an arms race.
| Token | Value | Use |
|---|---|---|
--ss-z-pop | 600 | Popovers, dropdown menus |
--ss-z-overlay | 1000 | Dialog scrim + dialog |
--ss-z-toast | 1400 | Toasts / audit badges above everything |
Focus
One focus ring everywhere — surface gap + brand line.
| Token | Value | Use |
|---|---|---|
--ss-focus-ring | 0 0 0 2px var(--ss-color-surface), 0 0 0 4px var(--ss-color-brand) | One focus ring everywhere — surface gap + brand line |
--ss-focus-ring-width | 3px | Inner focus wash spread |
More foundations
Three foundations have grown large enough to earn their own pages. They follow the same token discipline as everything above.
Toggle axes
Global customization knobs. One knob writes a data-attribute on the <html> root, the token layer swaps values, and every element renders under the new tier without any markup or behavior change. Motion pace is the reference `approved` axis; the five below (Density, Weight, Radius, Elevation, Contrast) ship `proposed` this round for evaluation on real UI.
Click a tier below to swap it live on this app; the choice persists in localStorage, no-flash on reload. Consumer apps read the same registry via /api/axes.json or /api/registry.json under an axes key.
data-ss-motiondata-ss-densitydata-ss-weightdata-ss-radiusdata-ss-elevationdata-ss-contrast