Harness design system · v3

Quiet, consistent, precise.

An instrument is made of one material. Nothing decorative survives that doesn't help you read the dial. v3 keeps every device the old system had — grain, glass, ambient light, the signature startup — and gives each one a budget.

What changed

v2 had the right devices and no budget

The previous system shipped gradient borders and coloured glows and ambient light and two competing accents and per-module feature hues, all at once. Every device was defensible on its own. Together they read as noise.

The structural fix

Colour became measurable

v2 stored colour in HSL, where lightness is not perceptual. A warm accent and a cool accent at the same L% land at visibly different weights, so the two could never be balanced by hand. v3 stores everything in OKLCH, where equal numbers look equal.

The behavioural fix

Every device got a ration

One gradient per viewport, and it has to read as light. Accent on at most 5% of the pixels. Two live hues, maximum. One radius scale. One focus treatment. The rules are countable, which means /design-review can enforce them.

TokenValueUse
Gradient bordersretiredThe single strongest "vibe-coded" tell. Replaced by a bevel.
Pill buttonsretiredPills are for badges and chips only. Buttons use --r-2.
Warm + cool pairretiredOne accent. A second hue is a feature mark, never a co-star.
Hue-tinted surfacesretiredFeature hues mark identity — a rule, a dot, a tick. Never a wash.
Breakpoint sizingreplacedFluid clamp() scales for type and space. Nothing snaps.
Ad-hoc durationsreplacedFive duration tokens and one house curve, shared by CSS and JS.

Foundations

Colour

A near-monochrome canvas where colour is an event. Every neutral carries a whisper of the project hue — chroma capped at 0.014, which is the number that separates tinted from coloured.

0

1

2

3

4

5

6

7

8

9

10

11

12

Neutral ramp · --n-0 … --n-12--n-0 is always canvas, --n-12 always strongest text — in both themes
--accent
--accent-hover
--accent-quiet
--accent-line
--accent-tint
Accent tokens · derived from three dialsdrag the dial — all of these move together

Do

Spend the accent on one primary action, the active nav state, links, the focus ring, and one ambient light source. That is the whole permitted list.

Don't

Tint card backgrounds, borders of inactive elements, headings, or icons. If the element isn't the current subject, it is neutral.

The dial

Three numbers retint every token on this page.

--accent: 0.780 0.135 78
every other accent token derives from that line.

TokenValueUse
--bg--n-0The canvas. Never #000 or #FFF.
--surface--n-1Cards and panels — one ramp step up.
--surface-raise--n-2Overlays, hover surfaces, secondary buttons.
--surface-sinkbelow --n-0Code wells — the only surface below the canvas.
--text / --text-muted / --text-faint--n-11 / --n-8 / --n-7Three text weights. There is no fourth.
--line-subtle / --line / --line-strong--n-3 / --n-4 / --n-5Hairlines. Depth comes from these, not shadow.

Foundations

Type

Three voices, and hierarchy comes from voice and scale rather than weight. Nothing in the system is bold.

display · Inter opsz 32, weight 500

Hierarchy from scale

body · Inter, cv11 + ss01

Body copy sits at --step-0 with a 1.65 line-height and a 70ch measure. Long-form prose loosens to 1.72.

micro · JetBrains Mono, +0.14em

Section labels · eyebrows · counters · table headers

The three voices
--step-7Hero
--step-5Page title
--step-3Section
--step-1Lede
--step-0Body
--step--1Control
--step--2Micro
Fluid scale · --step--2 … --step-7resize the window — every line interpolates, none snap

Foundations

Space & radius

Two viewport poles — 390px and 1440px — and everything between interpolates. Tailwind's own --spacing base unit is fluid, so every p-4 and gap-6 in the codebase breathes without a single edit.

--space-3xs
--space-2xs
--space-xs
--space-s
--space-m
--space-l
--space-xl
--space-2xl
--space-3xl
Space scale · --space-3xs … --space-3xl
--r-1 · 6pxchips, checkboxes
--r-2 · 10pxbuttons, inputs, menu items
--r-3 · 14pxcards, panels
--r-4 · 20pxfeature cards, modals
Radius · one scale, shared by every component--r-full is for badges and avatars only

Foundations

Depth

Depth is light physics: a bevel highlight where light would land, a hairline ring, and a two-step shadow ramp. Never a decorative gradient, never a soft grey blur.

1 · surface
2 · + bevel
3 · + ring
4 · + lift
The four layers of a controleach layer added left to right
flatcanvas
raised--surface + --lift-1
overlay--surface-raise + --lift-3
Elevation · three levels onlya card separates by hairline + one ramp step, not by shadow

Components

Controls

Every variant shares one anatomy, so they read as siblings rather than strangers. And every interactive element must express all five states from the same tokens — that contract is ship-blocking.

one per view
the default action
hairline only
toolbars, menus
destructive
Variants · one anatomy, only the surface changes
Sizes · 30 / 36 / 44pxheights are fixed so controls share a baseline with fixed-size icons
The five-state contracthover and focus these — every control in the system behaves identically
Fields share the button's radius, ring and states

Do

Use exactly one primary per view. Two primaries means neither is — demote the second to secondary or outline.

Don't

Give a control a bespoke hover, radius, or focus ring. If a component needs to look different, it needs a token, not an exception.

Foundations

Motion

The part of v2 that was already right — kept, and made repeatable. Five durations, one house curve, and choreography rules that state why the old sites felt good so the next one does too.

Duration tokens

Distance scales with duration — that pairing is the rule.

  • --dur-1120ms
  • --dur-2200ms
  • --dur-3320ms
  • --dur-4520ms
  • --dur-5900ms

Every row uses --ease-out, the house curve. Under prefers-reduced-motion all five snap to their end state.

Choreography

  • Distance scales with duration. An 8px move gets --dur-2; a 24px move gets --dur-4.
  • Two properties maximum per element — opacity plus one transform.
  • Stagger units are 40ms inside a list, 70ms between sections, with the total capped at 400ms so long lists let their tail arrive together.
  • One scene per page, not per section.
  • Hover changes light, not layout. Glow, opacity, bevel — never a transform that reflows.

The signature startup

Every harness site opens with the same orchestrated anime.js timeline — the one that just ran above. The eyebrow settles, the display headline cascades character by character out of per-word clip masks, then sections stagger in as a wave. Under 1.2s.

// mark the hero, then call once on mount
<p data-intro-eyebrow>…</p>
<h1 data-intro-headline>…</h1>
<section className="reveal">…</section>

introTimeline(pageRef.current)

The page load belongs to anime.js. Motion keeps in-page presence and layout. Reduced motion snaps everything visible.

Inventory

Components

Built only from semantic tokens. A component that needs a raw value is a component that will drift.

Interactive

Hover me

Border, surface and lift shift together at --dur-2.

Static

Hairline + one step

No gradient wash. No accent border unless it is the active item.

Cards · a surface, not a poster

Accent budget

5

% of viewport

Live hues

2

maximum

Gradients

1

per viewport

Stat tiles · the value dominates--step-3 display, tabular figures, micro label

Nothing here yet

A designed zero-state is ship-blocking. An empty rectangle is a craft-floor failure.

Empty state · tinted fill, hue chip, inline actionnever dashed borders · max one per screen
62
88
45
71
54
93
38
Chart series · walk the accent rampa fifth series means the chart is wrong

Enforcement

The countable rules

A rule that can't be counted can't be enforced, and a design system nobody enforces is a mood board. These are what /design-review checks before /ship lets anything go live.

TokenValueUse
Gradient budget≤ 1 / viewportAnd it must read as light — ambient field or mask fade. Zero gradient borders.
Colour budget≤ 5% accentAccent pixels as a share of any viewport. Halved from v2's 10%.
Live hues≤ 2The accent plus at most one feature mark. Status colours are exempt.
Neutral chroma≤ 0.014Above this the canvas reads coloured rather than tinted.
Radius scale1 per viewEvery button, input and menu item shares a radius. No stray pills.
Control states5 of 5rest · hover · press · focus-visible · disabled, from shared tokens.
Text weights3--text, --text-muted, --text-faint. There is no fourth.
Hard-coded px0Type and spacing come from steps. Fixed px only for control heights.

Where this lives

  • DESIGN.md — the spec. Single source of truth; this site is its showcase.
  • templates/nextjs-app · templates/vite-app — the executable copies. New projects start here.
  • TOOLKIT.md — the library shortlist, and which registries are safe to pull from.