Skip to content

Reference · craft

The six directions

craft/references/directions.md186 linesupdated 16 Sept 2026

Pick exactly one. Each direction file sets the same semantic roles (light + dark), so components never change when the direction does. Source of truth: packages/tokens/css/directions/.css (template copy: src/styles/void/directions/.css). Read its header comment before building: it lists fonts, signature and don'ts.

Switching or installing a direction

css
/* src/app/globals.css — order matters */
@import "tailwindcss";
@import "../styles/void/base.css";
@import "../styles/void/directions/editorial.css";   /* exactly one */
tsx
// src/app/layout.tsx — variable names MUST match the var() hooks in the direction file
import { Newsreader, IBM_Plex_Mono } from "next/font/google";
const serif = Newsreader({ subsets: ["latin"], variable: "--font-newsreader", display: "swap", style: ["normal", "italic"] });
const mono = IBM_Plex_Mono({ subsets: ["latin"], weight: ["400", "500"], variable: "--font-plex-mono", display: "swap" });
// <html className={`${serif.variable} ${mono.variable}`}>

Then set direction: "editorial" in src/site.config.ts (theme-color, OG image and manifest read it) and theme.default to the direction's scheme.

Knobs each direction sets

Knob precision editorial swiss warm terminal instrument
--radius 6px 2px 0 10px 0 6px
--weight-display / --weight-heading 500 / 500 400 / 450 700 / 700 600 / 600 600 / 600 600 / 600
--tracking-display (× base tracking) 1 0.35 0.9 0.6 1.1 0.5
--label-tracking 0.08em 0.1em 0.06em 0.06em 0.12em 0.06em
durations fast / base / slow / slower 100/150/200/450 150/250/400/600 0/100/150/300 150/220/320/500 60/100/150/300 100/150/200/300
--grain-opacity light / dark 0 / 0.035 0.045 / 0.06 0 / 0 0.03 / 0.05 0 / 0.04 0 / 0
brand (light) indigo oklch(54.5% .19 272) oxblood oklch(46% .15 22) signal red oklch(56.5% .22 29) clay oklch(55% .155 40) amber oklch(80% .165 72) (dark text on solids) blue oklch(53% .17 255)
default scheme dark light light light dark system
extra --ease-out: linear --section-y 40→72px, --container-page 90rem

Serif and geometric faces need less negative tracking, which is why editorial is 0.35× and terminal 1.1×.


precision — calm, exact, fast

  • Use: dev tools, AI/infra products, pro SaaS, technical launch pages. Avoid: playful consumer, long-form reading.
  • Measured references: Linear (Inter 510, −0.022em, #08090a, rgba(255,255,255,.08) hairlines, 8px radius), Vercel (Geist 400, −0.06em, ring-as-border, blue on 2 elements), Raycast.
  • Fonts: Geist (display 500, UI 13px) + Geist Mono (labels, IDs, kbd). ~51 KB.
    tsx
    import { Geist, Geist_Mono } from "next/font/google";
    const sans = Geist({ subsets: ["latin"], variable: "--font-geist-sans", display: "swap" });
    const mono = Geist_Mono({ subsets: ["latin"], variable: "--font-geist-mono", display: "swap" });
  • Signature details: label-mono as the UI voice; 1px white-alpha hairlines; depth from lighter surfaces and inset top highlight; Kbd chips; two-tone H2s; a real product UI (or honest DOM mock) as the hero visual; brand on ≤ 2% of pixels.
  • Don't: gradient buttons, pill buttons everywhere, serif, illustrations, glass cards, a second accent.
tsx
<section className="relative overflow-clip border-b border-line-subtle">
  <div aria-hidden="true" className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-144 bg-[radial-gradient(60%_70%_at_20%_0%,color-mix(in_oklch,var(--brand)_12%,transparent),transparent_70%)]" />
  <Container className="pt-16 pb-20 lg:pt-32 lg:pb-24">
    <Eyebrow>Changelog · v2.4 shipped</Eyebrow>
    <h1 className="mt-6 max-w-[16ch] text-display-lg text-fg">Deploy previews for every branch in 14 seconds.</h1>
    <p className="mt-6 max-w-[44ch] text-lg text-fg-muted">Push a branch, get a URL. Logs, env and rollbacks live in one place.</p>
    <div className="mt-10 flex flex-wrap items-center gap-6">
      <Button href="/signup" size="lg">Start deploying</Button>
      <code className="font-mono text-sm text-fg-muted">$ npx acme deploy</code>
    </div>
    <div className="mt-16 overflow-clip rounded-xl border border-line bg-surface shadow-3 lg:mt-24">{/* real product UI */}</div>
  </Container>
</section>

editorial — literary, considered, slow

  • Use: publications, essays, research, studios, premium brands, long-form docs. Avoid: dense data apps.
  • Measured references: Anthropic (ivory #faf9f5, serif body 20px, clay accent on 1 element, split hero), Granola (serif display 86px at 0.93 leading, −0.02em, sections 112–160px), Paco (1.75 body leading, serif italic emphasis).
  • Fonts: Newsreader (opsz axis: display and text from one file; italic for decks) + IBM Plex Mono (kickers, bylines). Snippet at top of this file.
  • Signature details: body text-lg (17–19px) at 1.6–1.65 in max-w-prose; structure from rules (border-t border-line-strong on section heads) not boxes; mono uppercase kickers; always-underlined links (TextLink, decoration-brand-line underline-offset-4); italic deck under the H1; drop cap (first-letter:float-left first-letter:mr-2 first-letter:text-[3.6em] first-letter:leading-[0.8]); grain on paper.
  • Don't: cards, pills, icon grids, sans headlines, gradients, scaling or bouncy motion. Danger shares the red family with the brand: pair it with an icon and text.
tsx
<Container className="grid gap-10 border-b border-line-strong pt-20 pb-16 lg:grid-cols-12 lg:pt-32">
  <div className="lg:col-span-7">
    <p className="label-mono text-fg-muted">Essay · 14 min read</p>
    <h1 className="mt-5 text-display-xl text-fg">The quiet economics of maintenance</h1>
  </div>
  <p className="text-xl italic text-fg-muted lg:col-span-5 lg:self-end">
    Why the cheapest code is the code you already understand, and what it costs to forget that.
  </p>
</Container>

swiss — rational, confident, poster-like

  • Use: agencies, architecture, type/design tools, events, portfolios, data journalism. Avoid: soft consumer brands.
  • Measured references: basement.studio (one weight 600 at 0.90 leading, −0.04em, one #ff4d00 accent + selection, radius 0), hellohello (one weight, size-only hierarchy, numbered editorial lists with hairlines), PlanetScale (0 radius, logo table).
  • Fonts: Schibsted Grotesk (700 display, 400 body) + JetBrains Mono (indices "01 —", captions).
    tsx
    import { Schibsted_Grotesk, JetBrains_Mono } from "next/font/google";
    const sans = Schibsted_Grotesk({ subsets: ["latin"], variable: "--font-schibsted", display: "swap" });
    const mono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains-mono", display: "swap" });
  • Signature details: 12-col grid with label col-span-3 / content col-span-9; flush left, ragged right; border-line is full-contrast ink here; numbered sections; huge size contrast (text-sm next to text-display-xl); hover = invert (hover:bg-fg hover:text-bg); radius 0; linear, near-instant motion.
  • Don't: shadows, any rounding, centered body, more than 2 weights, decorative icons.
tsx
<Container className="grid gap-y-8 border-t border-line pt-6 pb-24 lg:grid-cols-12">
  <p className="font-mono text-sm text-fg-muted lg:col-span-3">01 — Studio</p>
  <h1 className="text-display-xl text-fg lg:col-span-9">We design buildings that age in public.</h1>
  <p className="max-w-[48ch] text-md text-fg-muted lg:col-span-5 lg:col-start-4">Forty-one projects across nine cities since 2011. Housing, libraries, one bridge.</p>
</Container>

warm — approachable, tactile, human

  • Use: consumer SaaS, productivity/notes, education, health, community. Avoid: security/infra, luxury, dense data.
  • Measured references: Cursor (warm #f7f7f4, fg at 60/50/40%), Family (40–72px device radii, 1s out-expo reveals), Granola (olive accent, positive small-size tracking).
  • Fonts: Bricolage Grotesque (display ≥ 32px only; never request extra axes: 40 → 128 KB) + Figtree (UI/body) + JetBrains Mono (preload: false).
    tsx
    import { Bricolage_Grotesque, Figtree, JetBrains_Mono } from "next/font/google";
    const display = Bricolage_Grotesque({ subsets: ["latin"], variable: "--font-bricolage", display: "swap" });
    const sans = Figtree({ subsets: ["latin"], variable: "--font-figtree", display: "swap" });
    const mono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains-mono", display: "swap", preload: false });
  • Signature details: cards surface-card on bg-bg-subtle with 24px padding; bg-brand-subtle text-brand-text callouts; sentence case; ease-spring for panels, ease-spring-bouncy only for success moments; grain on hero; one illustration style (duotone brand + fg) if any.
  • Don't: neon, pure grays, sharp corners, 3D blobs, heavy shadows, Poppins/Nunito.
tsx
<Container className="grid items-center gap-12 pt-16 pb-20 lg:grid-cols-12 lg:pt-28">
  <div className="lg:col-span-6">
    <h1 className="max-w-[14ch] text-display-lg text-fg">Notes that write the follow-up for you.</h1>
    <p className="mt-5 max-w-[42ch] text-lg text-fg-muted">Granular enough for a 1:1, calm enough for a Monday. Works offline.</p>
    <Button href="/download" size="lg" className="mt-8">Download for Mac</Button>
  </div>
  <div className="surface-card p-6 lg:col-span-6">{/* real note UI */}</div>
</Container>

terminal — raw, fast, honest

  • Use: CLIs, dev infra, hacker tools, hackathons, developer launch pages. Avoid: mainstream audiences, long reading.
  • Measured references: PlanetScale (system mono, 0 fonts, 1.7 MB page), the owner's DevEx landing (boot-log hero, amber at 1–2% coverage, tmux status line).
  • Fonts: Geist Mono only (~23 KB). Alt: JetBrains Mono under the same variable name.
    tsx
    import { Geist_Mono } from "next/font/google";
    const mono = Geist_Mono({ subsets: ["latin"], variable: "--font-geist-mono", display: "swap" });
  • Signature details: $/> prompts in text-brand-text; bracketed actions [ deploy ]; hard offset shadow (shadow-3) on one focal element; solid 1px borders; amber solids take dark text (text-fg-on-brand handles it); warning is yellower so "pending" never reads as the CTA; box-drawing dividers; tabular timestamps; a blinking block caret in CSS steps(1).
  • Don't: rounded corners, soft shadows, gradients, serif, emoji, glass, typewriter headlines (type the log, not the H1).
tsx
<Container className="pt-16 pb-20 lg:pt-28">
  <p className="label-mono text-fg-subtle">acme/cli · v3.2.0 · MIT</p>
  <h1 className="mt-6 max-w-[18ch] text-display text-fg">Ship infra from a single YAML file.</h1>
  <div className="mt-10 max-w-xl border border-line bg-surface p-4 font-mono text-sm shadow-3">
    <p><span className="text-brand-text">$</span> curl -fsSL acme.sh | sh</p>
    <p className="text-fg-muted">✓ installed acme 3.2.0 in 4.1s</p>
  </div>
</Container>

instrument — dense, data-first

  • Use: analytics, admin, finance, ops consoles, internal tools, docs for data products. Avoid: marketing landing pages with big type.
  • Measured references: Attio (14px w500 workhorse, 0.95 display leading, navy-tinted shadows, blue-tinted neutrals), Supabase (oklch tokens, 14px w450).
  • Fonts: IBM Plex Sans + IBM Plex Mono (weights 400/500).
    tsx
    import { IBM_Plex_Sans, IBM_Plex_Mono } from "next/font/google";
    const sans = IBM_Plex_Sans({ subsets: ["latin"], variable: "--font-plex-sans", display: "swap" });
    const mono = IBM_Plex_Mono({ subsets: ["latin"], weight: ["400", "500"], variable: "--font-plex-mono", display: "swap" });
  • Signature details: 13–14px UI; 36px table rows; sticky table header on bg-bg-subtle; right-aligned tabular-nums slashed-zero; delta chips Badge tone="success"; panels split by border-line-subtle; shadows only on floating layers; filters in the URL; section-y is compact (40→72px).
  • Don't: hero-size type, decorative gradients, scroll reveals, a second accent in chrome (charts use a separate palette).
tsx
<Container className="py-10">
  <div className="flex items-end justify-between border-b border-line-subtle pb-4">
    <h1 className="text-2xl text-fg">Revenue</h1>
    <p className="font-mono text-xs text-fg-muted tabular-nums">Updated 09:41 UTC</p>
  </div>
  <dl className="grid grid-cols-2 gap-px bg-line-subtle lg:grid-cols-4">{/* KPI tiles: bg-bg p-4 */}</dl>
</Container>

Blending rule

Never blend two directions. If the brief pulls two ways, pick the one matching the audience and borrow at most one detail from the other as a documented override in globals.css (for example editorial's italic deck inside precision). More than one override means you picked the wrong direction.