Skip to content

Design Tokens

Karthik P edited this page Oct 15, 2026 · 1 revision

Design Tokens

All of the web app's design tokens live in one file, apps/web/app/globals.css: 23 CSS custom properties on :root hold the values, and a Tailwind CSS 4 @theme inline block turns them into utilities such as bg-background, text-match and rounded-xl. The tokens follow shadcn/ui naming, with three additions for the brand (match, match-soft, highlight) and one value used only by the logo (highlight-deep). There is a single light theme: no dark values, chart colours or sidebar tokens exist. The marketing site keeps its own small token set in site/styles.css, copied from the same values. This page lists every token, its role and the utility it produces. For how they are used, see Visual System.

How the file is organised

@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";

@theme inline { /* Tailwind theme variables that point at the :root tokens */ }

:root { /* the token values */ }

@layer base { /* global element styles */ }
Part Purpose
@import "tailwindcss" Tailwind CSS 4 with its default theme (spacing, type scale, shadows, breakpoints).
@import "tw-animate-css" animate-in, fade-in-0, zoom-in-95 and related utilities used by the dialog and menu.
@import "shadcn/tailwind.css" From the shadcn package: the data-open, data-closed, data-checked, data-unchecked, data-selected, data-disabled, data-active, data-horizontal and data-vertical custom variants, accordion keyframes, and the no-scrollbar, scroll-fade-* and shimmer-* utilities. It defines no colour tokens.
@theme inline Declares Tailwind theme variables. inline makes a utility emit the referenced value directly (background-color: var(--background)) instead of the theme variable, so the utility always follows the current :root value.
:root The source of truth for every value.
@layer base Applies border-border and outline-ring/50 to every element, bg-background text-foreground to body, font-sans to html, and text-wrap: balance to h1 and h2.

Brand palette mapping

The six colours in the brand guide are app tokens. Several roles share one value.

Brand colour Hex Tokens Typical utilities
Ink #1c2a23 --foreground, --card-foreground, --popover-foreground, --primary, --secondary-foreground, --accent-foreground text-foreground, bg-primary
Paper #eef1ec --background bg-background
Sheet #fafbf8 --card, --popover bg-card, bg-popover
Marker #f3de6e --highlight bg-highlight
Marker, twice #e2b92a --highlight-deep none (not in @theme; used as var(--highlight-deep) in the logo)
Match #2f6b4f --match, --ring bg-match, text-match, ring-ring

Colour tokens

Each --color-<name> theme variable produces the full set of Tailwind colour utilities for <name>: bg-, text-, border-, ring-, outline-, fill-, stroke-, divide-, accent-, caret-, decoration-, placeholder-, from-/via-/to- and so on, each accepting an opacity modifier such as /50.

Surfaces and text

Swatch Token Value Role Theme variable Example utilities
--background #eef1ec Page background (Paper) --color-background bg-background
--foreground #1c2a23 Default text (Ink) --color-foreground text-foreground, ring-foreground/10, border-foreground
--card #fafbf8 Card surface (Sheet) --color-card bg-card
--card-foreground #1c2a23 Text on cards --color-card-foreground text-card-foreground
--popover #fafbf8 Dialogs, menus, toasts --color-popover bg-popover
--popover-foreground #1c2a23 Text on popovers --color-popover-foreground text-popover-foreground

Actions

Swatch Token Value Role Theme variable Example utilities
--primary #1c2a23 Primary button, default badge, checked switch, done wizard step --color-primary bg-primary, hover:bg-primary/80, text-primary
--primary-foreground #f4f6f1 Text on primary and on the match badge --color-primary-foreground text-primary-foreground
--secondary #e2e7df Secondary button and badge --color-secondary bg-secondary
--secondary-foreground #1c2a23 Text on secondary --color-secondary-foreground text-secondary-foreground
--accent #dde6da shadcn accent surface; not used by any component today --color-accent bg-accent
--accent-foreground #1c2a23 Text on accent; not used today --color-accent-foreground text-accent-foreground
--destructive #b4432f Errors, failed verdicts, destructive button, invalid fields --color-destructive text-destructive, bg-destructive/10, border-destructive

There is no --destructive-foreground: the destructive button and badge use text-destructive on a 10% tint of the same colour.

Neutrals and lines

Swatch Token Value Role Theme variable Example utilities
--muted #e3e8e0 Hover rows, notices, skeletons, progress track, active nav item --color-muted bg-muted, bg-muted/50
--muted-foreground #56655c Secondary text, placeholders, list markers --color-muted-foreground text-muted-foreground
--border #d3dacf Default border colour (applied to every element by the base layer), separators --color-border border-border, bg-border, divide-y
--input #c6cfc2 Input borders, unchecked switch track --color-input border-input, bg-input
--ring #2f6b4f Focus rings and outlines (Match green) --color-ring ring-ring/50, border-ring, outline-ring/50

Brand additions

Swatch Token Value Role Theme variable Example utilities
--match #2f6b4f Match verdicts, progress indicator, done checks, checkbox accent --color-match bg-match, text-match, accent-match
--match-soft #dcebdf Soft match tint; defined but not used by any component today --color-match-soft bg-match-soft
--highlight #f3de6e Highlighter yellow for evidence in reasons; the logo check --color-highlight bg-highlight/70
--highlight-deep #e2b92a The overlap where the logo's two strokes cross. The CSS comment reads "The highlighter passing twice: only where the logo's two strokes cross." none none; read as var(--highlight-deep)

Light and dark values

Every colour above has one value. globals.css has no .dark selector or prefers-color-scheme block, so there is no dark column to document. See Visual System for what that means for the dark: classes inside the primitives.

Radius

Token Definition Resolved Utility Used by
--radius 0.375rem 6 px (base value) Also passed to Sonner as --border-radius
--radius-sm calc(var(--radius) * 0.6) 0.225rem (3.6 px) rounded-sm Focusable section headings
--radius-md calc(var(--radius) * 0.8) 0.3rem (4.8 px) rounded-md Menu items, skeletons, alert dialog media; caps the small button sizes
--radius-lg var(--radius) 0.375rem (6 px) rounded-lg Buttons, inputs, menus, notices, ScoringHelp
--radius-xl calc(var(--radius) * 1.4) 0.525rem (8.4 px) rounded-xl Cards, alert dialog, home skeletons
--radius-2xl calc(var(--radius) * 1.8) 0.675rem (10.8 px) rounded-2xl Not used today
--radius-3xl calc(var(--radius) * 2.2) 0.825rem (13.2 px) rounded-3xl Not used today
--radius-4xl calc(var(--radius) * 2.6) 0.975rem (15.6 px) rounded-4xl Badges

rounded-xs, rounded-full and rounded-none keep Tailwind's defaults.

Fonts

Token Value Utility Notes
--font-schibsted Set by next/font on <html> none Created by Schibsted_Grotesk({ variable: "--font-schibsted", subsets: ["latin"] }) in app/layout.tsx.
--font-sans var(--font-schibsted) font-sans Applied to <html> by the base layer, so it is the default everywhere.
--font-heading var(--font-schibsted) font-heading Used by CardTitle and AlertDialogTitle. Same family as font-sans; kept as a separate hook.

--font-mono keeps Tailwind's default stack.

Component-scoped variables

These are set inside components rather than in globals.css.

Variable Set in Default Purpose
--card-spacing card.tsx --spacing(4) (1rem); --spacing(3) when size="sm" Card gap and padding. App cards override it with [--card-spacing:--spacing(5)].
--normal-bg, --normal-text, --normal-border, --border-radius sonner.tsx (inline style) var(--popover), var(--popover-foreground), var(--border), var(--radius) Sonner's toast colours and radius.
--available-height, --transform-origin Base UI menu positioner Set at runtime Menu max height and zoom origin (max-h-(--available-height), origin-(--transform-origin)).

Not present

Checked against the file, these common shadcn token groups do not exist, so their utilities are not available:

  • Chart colours (--chart-1 … --chart-5).
  • Sidebar tokens (--sidebar, --sidebar-foreground, --sidebar-primary, …).
  • --destructive-foreground.
  • Custom shadow, spacing, breakpoint or font-size tokens. Tailwind's defaults are used for all of them.

shadcn configuration (components.json)

apps/web/components.json tells the shadcn CLI how components were generated and where to put new ones.

Key Value Meaning
style base-nova The shadcn style built on Base UI primitives
rsc true Adds "use client" where a component needs it
tsx true TypeScript components
tailwind.config "" No JavaScript config (Tailwind 4 is configured in CSS)
tailwind.css app/globals.css Where tokens live
tailwind.baseColor neutral The base palette the CLI started from; the values have since been replaced with the brand's green-greys
tailwind.cssVariables true Components use token utilities (bg-primary), not literal colours
tailwind.prefix "" No utility prefix
iconLibrary lucide Icons come from lucide-react
rtl false No right-to-left variants
menuColor / menuAccent default / subtle Menu styling presets
aliases.components @/components
aliases.ui @/components/ui
aliases.utils @/lib/utils Resolves to the cn re-export
aliases.lib @/lib
aliases.hooks @/hooks The folder does not exist yet
registries {} No third-party registries

Marketing site tokens (site/styles.css)

The static site cannot import the app's CSS, so it repeats the values it needs. Its stylesheet says the tokens "come from the app's theme". It uses plain custom properties with no utility layer.

Swatch Token Value Role App equivalent
--paper #eef1ec Page background --background
--sheet #fafbf8 Example-run sheet, outline buttons, command box --card
--ink #1c2a23 Text, install band background, the heavy rule at the top of each list --foreground
--ink-soft #56655c Secondary text --muted-foreground
--ink-faint #7d8a81 Struck-out titles on the wall only (large text, 3.4 : 1 on the sheet) none
--rule #d3dacf Hairlines --border
--rule-strong #b7c1b3 Button and command borders, wall separators none
--match #2f6b4f Links, focus outline, match badge, "checking" title --match
--marker #f3de6e Highlighter stroke on matched titles, focus outline on the dark band --highlight
--on-ink #f4f6f1 Text on Ink --primary-foreground
--on-ink-soft #b9c4bb Secondary text on Ink none
Token Value Role
--font "Schibsted Grotesk", ui-sans-serif, system-ui, sans-serif Body and headings
--mono ui-monospace, "Cascadia Mono", "SFMono-Regular", Menlo, Consolas, monospace Install commands
--step-0 0.875rem Small text, buttons
--step-1 1.0625rem Body, h3
--step-2 1.3125rem Lead, counts, verdict title
--step-3 2.25rem h2
--step-4 3.75rem Step numbers
--gutter 1rem, 2rem from 48rem Side padding
--max 72rem Content width (the same as the app's max-w-6xl)
--radius 6px Buttons, command box, screenshots (the same as the app's --radius)
--section clamp(4rem, 3rem + 5vw, 7.5rem) Vertical space between sections
--lh 1.3 on .wall, 1.55 on .key-list .t Line height used to size the strike and highlighter backgrounds
--strike, --marker-stroke, --check Gradients and an SVG mask on .t The strike line, the highlighter stroke and the check glyph
--crop-x, --crop-w, --crop-h Inline on each .shot Crop the screenshots to the app's content column

The site has no token for Marker, twice; the inline logo in index.html uses the literal #e2b92a. The site sets color-scheme: light.

Adding a token

  1. Add the value to :root in apps/web/app/globals.css, named after its role rather than its colour (for example --warning, not --amber). If it pairs with text, add the -foreground partner as well.
  2. Expose it to Tailwind by adding a line to @theme inline, using the namespace that produces the utilities you need:
    • --color-warning: var(--warning); gives bg-warning, text-warning, border-warning, …
    • --radius-<name> gives rounded-<name>; --font-<name> gives font-<name>; --shadow-<name> gives shadow-<name>. Skip this step for a value that only CSS or SVG reads with var(), as --highlight-deep does.
  3. Use the utility in components. Prefer opacity modifiers (bg-warning/10) to new tint tokens.
  4. Mirror it on the marketing site if the site needs it: add a matching property to :root in site/styles.css.
  5. Update the brand guide (docs/brand/README.md) if it is a brand colour, and this page.
  6. Check contrast against --background and --card (4.5 : 1 for body text).

If dark mode is added later, every token here needs a dark value in the same change; see Visual System.

Clone this wiki locally