Ship/Now

Design · 3 min

Design tokens

One accent, one typeface, and a set of variables that make dark mode a redefinition rather than a second stylesheet.

Tailwind v4 has no config file. Tokens are declared in CSS and Tailwind emits a class for each one.

@theme {
  --color-ink: #0b0c0f;
  --color-ink-2: #5f646a;
  --color-ink-3: #9297a0;
  --color-line: #e6e8ea;
  --color-paper: #ffffff;
  --color-paper-2: #f4f5f6;
  --color-brand: #f8430a;
  --color-brand-soft: #fdece4;

  --radius-card: 12px;
  --radius-panel: 20px;
}

--color-ink-2 gives you text-ink-2, bg-ink-2, border-ink-2.

Dark mode

Because every surface reads a token, the whole site inverts by redefining them:

[data-theme="dark"] {
  --color-ink: #f2f3f4;
  --color-ink-2: #a2a8b0;
  --color-line: #24272c;
  --color-paper: #101216;
  --color-paper-2: #171a1f;
  --color-brand: #ff5a24;
}

No dark: prefix on four hundred elements, and no second set of classes to keep in sync. The accent lifts slightly because the same orange reads dimmer on near-black than it does on white.

Resolve the attribute on the server from a cookie and put it on <html> during render. Do it in a client effect and the page paints light, then flips, which is the flash everybody notices and nobody mentions.

Restraint

The rules the kit follows, which are worth keeping:

  • One accent. Orange appears on the logo, one emphasised phrase, and the active state. When everything is accented, nothing is.
  • Radius varies by element. Buttons are pills, small cards are nearly square, only large panels get a generous corner. One radius everywhere is a tell.
  • Border or shadow, not both. Both on the same element is the stacked card look that reads as generated.
  • No glowing gradient. Especially not purple to blue.
  • Real icons, drawn as line work. Emoji glyphs render differently on every platform and look like clip art at 16 pixels.

Typography

Two faces: a display sans and a mono. next/font/google loads them in layout.tsx, which self-hosts the files and emits the CSS variable used in @theme, so there is no layout shift and no third-party request.

Deliberately not Inter, Geist or Space Grotesk. They are excellent and they are the default in every generated interface, which is exactly the problem.

Something wrong or missing on this page? Tell us.