Design system
The system this site is made of.
Every value on saer.app comes from one token file. This page is generated from it, and Saer audits the result. Two colours carry meaning, one typeface carries the voice, and every element earns its place.
This site passes its own audit.
Saer 0.2.0-beta.3-rc.9 on this website's source, 30 October 2026. Accessibility checks are static: not a statement of WCAG conformance.
| Colour tokens reconstructed | 37 |
|---|---|
| References resolved | 134 of 134 |
| Exact colour drift | 0 |
| Near-matches | 0 |
| Dead colour tokens | 0 |
| Type: exact drift and off-scale sizes | 0 |
| Spacing and sizing | 0 |
|---|---|
| Radius | 0 |
| Shadows | no scale declared |
| Z-index | 0 |
| Breakpoints | 0 |
| Border widths | 0 |
| Opacity | 0 |
| Motion | 0 |
| Failing contrast pairs, every context | 0 |
|---|---|
| Passing contrast pairs | 18 |
| Focus outline removed with no replacement | 0 of 134 |
| Targets written below 24 px | 0 |
| Motion with no reduced-motion fallback | 0 |
saer scan packages/saer-web --output /tmp/saer-siteShadows are not part of this system: depth comes from surfaces and rules, so Saer reports no shadow scale, and says so rather than inventing one.
Two colours that mean something.
Green marks what is verified, shipped, or the next action. Lavender marks inference, testing and intelligence. Charcoal and cream are the surfaces. Where a brand colour fails contrast, a darker or lighter variant is derived in OKLCH at the same hue, and tested.
- --green-400#CCF676Kawasaki green. Brand. Means verified, shipped, go. A fill or a mark, never text on a light surface (1.13:1 on cream).
- --green-700#4A6300Derived from green 400 in OKLCH (same hue, lower lightness) so green can carry text on light surfaces: 6.25:1 on cream, 5.20:1 on neutral.
- --lavender-400#A99FECSoft lavender. Brand. Means inferred, in testing, intelligence. A fill or a mark on light surfaces; text on charcoal (7.79:1).
- --lavender-700#5F4FB0Derived from lavender 400 in OKLCH for text on light surfaces: 5.95:1 on cream, 4.95:1 on neutral.
- --charcoal-600#6A6F72Derived. Strong boundary on charcoal (3.63:1), for inputs and controls that need a visible edge (WCAG 1.4.11).
- --charcoal-700#2E3236Derived. Hairline rule on charcoal. Decorative only.
- --charcoal-800#1C1F22Derived. Raised surface on charcoal.
- --charcoal-900#121416Deep charcoal. Brand primary dark surface and primary text on light surfaces (16.90:1 on cream).
- --cream-50#F6F5EFWarm cream. Brand primary light surface and primary text on charcoal.
- --neutral-200#DEE2D9Soft neutral. Sunken surface and hairline rule on light surfaces. Decorative contrast only (1.20:1 on cream).
- --grey-300#959B9ADerived from grey 500, lighter, for muted text on charcoal: 6.54:1 on charcoal, 5.86:1 on raised charcoal.
- --grey-500#6F7574Muted grey. Brand. Strong boundary on light surfaces (3.57:1 on neutral). Fails 4.5:1 for body text on cream (4.30:1), so muted text uses grey 600.
- --grey-600#5A605FDerived from grey 500, darker, for muted text on light surfaces: 5.87:1 on cream, 4.88:1 on neutral.
- --red-300#FF8A80Functional, not brand. Error text on charcoal (7.25:1 on raised charcoal). Only for validation errors.
- --red-700#B42318Functional, not brand. Error text on light surfaces (6.02:1 on cream). Only for validation errors.
- --white#FFFFFFRaised surface on cream: cards, inputs, the report frame.
Semantic roles, per surface
Components use roles, never the palette. Setting data-theme="dark" on any element remaps every role for that subtree.
| Role | Cream surface | Charcoal surface | Meaning |
|---|---|---|---|
| --color-bg | #F6F5EF | #121416 | Page surface. |
| --color-bg-raised | #FFFFFF | #1C1F22 | Cards, inputs, panels. |
| --color-bg-sunken | #DEE2D9 | #121416 | Wells, code, quiet bands. |
| --color-fg | #121416 | #F6F5EF | Primary text. |
| --color-fg-muted | #5A605F | #959B9A | Supporting text, captions, metadata. |
| --color-border | #DEE2D9 | #2E3236 | Hairline rules. Decorative. |
| --color-border-strong | #6F7574 | #6A6F72 | Control boundaries that must be seen (3:1). |
| --color-rule | #121416 | #F6F5EF | Structural rules: the top of a grid, a table head. |
| --color-action | #CCF676 | #CCF676 | Primary action fill. |
| --color-on-action | #121416 | #121416 | Text on the primary action. |
| --color-action-edge | #121416 | #CCF676 | Edge of the primary action on cream, where green alone has no contrast with the surface. |
| --color-verified | #CCF676 | #CCF676 | Fill for verified, deterministic, shipped. |
| --color-verified-fg | #4A6300 | #CCF676 | Text that means verified. |
| --color-on-verified | #121416 | #121416 | Text on a verified fill. |
| --color-inferred | #A99FEC | #A99FEC | Fill for inferred, in testing, in development. |
| --color-inferred-fg | #5F4FB0 | #A99FEC | Text that means inferred. |
| --color-on-inferred | #121416 | #121416 | Text on an inferred fill. |
| --color-focus | #5F4FB0 | #CCF676 | Focus ring. Passes 3:1 against every light surface. |
| --color-error-fg | #B42318 | #FF8A80 | Validation error text and the invalid control edge. |
| --color-mark-verified | #CCF676 | #4A6300 | Highlight band under verified words in display type; text crosses it. |
| --color-mark-inferred | #A99FEC | #5F4FB0 | Highlight band under inferred words in display type; text crosses it. |
Contrast, computed
WCAG 2.x ratios for every pair the system promises, computed from tokens.json on build. A test fails the build if any falls below its threshold (4.5:1 text, 3:1 boundaries and focus).
| Foreground on background | Needs | Cream | Charcoal |
|---|---|---|---|
| fg on bg | 4.5:1 | 16.90 | 16.90 |
| fg on bg-raised | 4.5:1 | 18.46 | 15.16 |
| fg on bg-sunken | 4.5:1 | 14.06 | 16.90 |
| fg-muted on bg | 4.5:1 | 5.87 | 6.54 |
| fg-muted on bg-raised | 4.5:1 | 6.42 | 5.86 |
| fg-muted on bg-sunken | 4.5:1 | 4.88 | 6.54 |
| on-action on action | 4.5:1 | 14.96 | 14.96 |
| on-verified on verified | 4.5:1 | 14.96 | 14.96 |
| on-inferred on inferred | 4.5:1 | 7.79 | 7.79 |
| verified-fg on bg | 4.5:1 | 6.25 | 14.96 |
| verified-fg on bg-raised | 4.5:1 | 6.83 | 13.41 |
| verified-fg on bg-sunken | 4.5:1 | 5.20 | 14.96 |
| inferred-fg on bg | 4.5:1 | 5.95 | 7.79 |
| inferred-fg on bg-raised | 4.5:1 | 6.50 | 6.98 |
| inferred-fg on bg-sunken | 4.5:1 | 4.95 | 7.79 |
| fg on mark-verified | 4.5:1 | 14.96 | 6.25 |
| fg on mark-inferred | 4.5:1 | 7.79 | 5.95 |
| error-fg on bg | 4.5:1 | 6.02 | 8.09 |
| error-fg on bg-raised | 4.5:1 | 6.57 | 7.25 |
| border-strong on bg | 3:1 | 4.30 | 3.63 |
| border-strong on bg-raised | 3:1 | 4.70 | 3.26 |
| action-edge on bg | 3:1 | 16.90 | 14.96 |
| focus on bg | 3:1 | 5.95 | 14.96 |
| focus on bg-raised | 3:1 | 6.50 | 13.41 |
| focus on bg-sunken | 3:1 | 4.95 | 14.96 |
Geometric, and exactly as loud as it needs to be.
Jost, an open-source geometric sans in the Futura tradition, carries the voice; Futura itself is not licensed for the web. Martian Mono carries data: tokens, counts, paths. Sizes are fluid between a 375 and a 1440 viewport, and every size on this site is one of these nine.
- --text-hero · 32 to 72px. The home statement below 64rem, set in four lines: sized to the viewport so its longest line fits at 320px. From 64rem it is two lines at text-4xl.Design intelligence
- --text-display · 48 to 144px. Display numerals. One per page at most.Design intelligence
- --text-4xl · 40 to 80px. H1 on inner pages.Design intelligence
- --text-3xl · 32 to 56px. H2.Design intelligence
- --text-2xl · 24 to 36px. H3.Design intelligence
- --text-xl · 20 to 26px. H4, card titles.Design intelligence
- --text-lg · 18 to 22px. Lede, H5.Design intelligence
- --text-base · 16 to 18px. Body.Design intelligence
- --text-sm · 14px. Metadata, supporting copy, form help.Design intelligence
- --text-xs · 12px. Mono labels, captions.Design intelligence
Line height
- --leading-display0.92
- --leading-tight1.05
- --leading-snug1.25
- --leading-normal1.5
- --leading-relaxed1.65
Tracking
- --tracking-display-0.035em
- --tracking-tight-0.02em
- --tracking-normal0em
- --tracking-mono-0.01em
- --tracking-caps0.12em
Weight
- --font-weight-light300
- --font-weight-regular400
- --font-weight-medium500
- --font-weight-semibold600
A 4px base, a 12-column grid, square corners.
Spacing steps grow from 4px. The page sits on a 12-column grid with a fluid gutter. Corners are square; circles are reserved for inference and status, so shape carries meaning too.
- --space-14px
- --space-28px
- --space-312px
- --space-416px
- --space-524px
- --space-632px
- --space-748px
- --space-864px
- --space-996px
- --space-10128px
- --space-gutter16 to 40px. Page edge and grid gap.
- --space-section64 to 128px. Vertical rhythm between sections.
Radius
- --radius-none0
- --radius-sm2px
- --radius-full999px
Motion
- --duration-instant1ms
- --duration-fast120ms
- --duration-base240ms
- --duration-slow480ms
- --duration-reveal800ms
- --ease-standardcubic-bezier(0.2, 0, 0, 1)
- --ease-outcubic-bezier(0.16, 1, 0.3, 1)
Motion explains a change of state, once. Under reduced motion every animation and transition is cut to a single frame.
Layering and borders
- --z-behind-1
- --z-sticky50
- --z-overlay60
- --z-skip100
- --border-width-hairline1px
- --border-width-rule2px
- --opacity-disabled0.5
Few components, each with one job.
Every interactive element has a visible focus ring on both surfaces. Try Tab.
Button
Button, on charcoal
Status
Certainty
Status, on charcoal
Callout
Neutral information.
True of the beta download.
Built, not yet shipped.
Not built.
Form controls and validation
Please check your email address.
Feedback
You’re on the list.
Success: a green rule, a plain sentence, focus moved here.
That didn’t send.
Errors say what happened and what to do, in words, never colour alone.
Code
saer scan .
# report: .saer/report.htmlInline --color-fg sits on the sunken surface.
Rules an agent can follow.
The same system, written for Claude, Codex and every other agent that edits this site: tokens as JSON, rules as a short list, and a lint that fails when they are broken.
npm run lint # design rules: no raw colour, no off-scale size, no serif, no em dash
npm test # tokens.css matches tokens.json; every contrast pair passes
npm run tokens # regenerate tokens.css after editing tokens.jsonRead the agent rules and tokens.json. The full human documentation lives in packages/saer-web/design-system/.