HTML & CSS

Design Tokens

A design token is a named design value — a color, a spacing size, a font — stored once and reused everywhere. In CSS they're just custom properties (variables). Change a token in one place, and your whole site updates.

The idea

What is a design token?

Instead of scattering #14b8a6 across 40 rules, you give that value a name — --color-accent — and use the name. The name is the token. It becomes a single source of truth: update the token once and everything that uses it changes. Designers call these “variables” in Figma; in CSS they're the same idea.

Hard-coded values (the hard way)

.btn      { background: #14b8a6; }
.link     { color: #14b8a6; }
.badge    { border: 2px solid #14b8a6; }
/* change the brand color? edit it in 3+ places... */

Tokens (the smart way)

:root { --color-accent: #14b8a6; }   /* define once */

.btn   { background: var(--color-accent); }
.link  { color: var(--color-accent); }
.badge { border: 2px solid var(--color-accent); }
/* change the brand color? edit ONE line. */
Step 1

Define & use your first tokens

Declare tokens on :root (the top of the document) with a --name, then read them anywhere with var(--name). This button's color, padding, and corner radius are all tokens.

<button class="btn">I'm built from tokens</button>

<style>
:root {
  --color-accent: #14b8a6;
  --space-md: 14px;
  --radius: 10px;
}

.btn {
  background: var(--color-accent);
  color: #fff;
  padding: var(--space-md) calc(var(--space-md) * 1.8);
  border: 0;
  border-radius: var(--radius);
  font-weight: 700;
  font-size: .95rem;
  font-family: inherit;
  cursor: pointer;
}
</style>
Naming: use -- then lowercase words with dashes. Group by purpose: --color-*, --space-*, --text-*, --radius-*.
Step 2

The usual token categories

Most design systems token-ize the same handful of things. Define a small, consistent set and reuse it everywhere.

Color

--color-bg#0f172a
--color-accent#14b8a6
--color-danger#f43f5e
--color-muted#e5e7eb

Spacing scale

--space-1: 4px
--space-2: 8px
--space-3: 16px
--space-4: 32px

Type scale

Heading — --text-xl
Subhead — --text-lg
Body text — --text-base
Caption — --text-sm
:root {
  /* color */
  --color-bg: #0f172a;  --color-accent: #14b8a6;
  --color-danger: #f43f5e;  --color-muted: #e5e7eb;

  /* spacing scale */
  --space-1: 4px;  --space-2: 8px;  --space-3: 16px;  --space-4: 32px;

  /* type scale */
  --text-sm: .875rem;  --text-base: 1rem;
  --text-lg: 1.5rem;   --text-xl: 2.25rem;

  /* radius & shadow */
  --radius-sm: 6px;  --radius-lg: 16px;
  --shadow: 0 10px 30px rgba(0,0,0,.2);
}
Step 3

Primitive vs. semantic tokens

Pro systems use two layers. Primitive tokens hold raw values (the palette). Semantic tokens describe a job and point at a primitive — this is called aliasing. Components use the semantic names, so swapping the palette doesn't touch them.

--teal-500: #14b8a6 --color-accent: var(--teal-500) .btn { background: var(--color-accent) }
:root {
  /* primitives — the raw palette (reference only) */
  --teal-500: #14b8a6;
  --slate-900: #0f172a;

  /* semantic — what each value is FOR (alias a primitive) */
  --color-accent: var(--teal-500);
  --color-bg:     var(--slate-900);
}

/* components only ever use the semantic names */
.btn { background: var(--color-accent); }
Why bother? Re-brand by changing --color-accent to a different primitive — every button, link, and badge updates, and nothing breaks because they never referenced the raw color directly.
Step 4

Theming with modes (light / dark)

This is where tokens shine. Define the same token names with different values under a theme selector. Your components never change — only the tokens do. Click the button to flip this demo.

Account settings

Every color here comes from a token. The card, text, and button don't know the theme — they just read var(--…).

<div class="theme" id="themeDemo">
  <div class="card">
    <h4>Account settings</h4>
    <p>Every color here comes from a token. The card, text, and button don't know the theme — they just read <code>var(--…)</code>.</p>
    <button class="btn">Save changes</button>
  </div>
  <button class="theme-toggle" onclick="document.getElementById('themeDemo').classList.toggle('dark')">Toggle light / dark</button>
</div>

<style>
/* light is the default set of token values */
.theme {
  --bg: #ffffff;  --surface: #f5f5f7;  --text: #1d1d1f;
  --muted: #6b7280;  --accent: #14b8a6;  --line: #e5e5ea;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: 14px; padding: 22px; transition: all .3s;
}
/* dark mode just redefines the SAME tokens */
.theme.dark {
  --bg: #0f172a;  --surface: #1e293b;  --text: #f1f5f9;
  --muted: #94a3b8;  --accent: #2dd4bf;  --line: #334155;
}

/* components read the tokens and never change */
.card { background: var(--surface); color: var(--text); border: 1px solid var(--line); padding: 18px; border-radius: 10px; }
.card h4 { color: var(--text); margin-bottom: 6px; }
.card p  { color: var(--muted); font-size: .88rem; margin: 0 0 14px; }
.btn  { background: var(--accent); color: #04201c; font-weight: 700; border: 0; padding: 9px 18px; border-radius: 8px; font-family: inherit; cursor: pointer; }
.theme-toggle { margin-top: 16px; background: rgba(20,184,166,.12); color: #5eead4; border: 1px solid rgba(20,184,166,.3); padding: 9px 16px; border-radius: 8px; font-weight: 600; font-family: inherit; cursor: pointer; }
</style>
Real-world tip: many sites set the theme on <html data-theme="dark"> and toggle it with one line of JavaScript — document.documentElement.dataset.theme = 'dark'.
Recap

Why tokens are worth it

Change once

Edit one token and it updates everywhere it's used — no find-and-replace.

Consistency

Everyone pulls from the same values, so spacing and color stay uniform.

Easy theming

Light/dark or whole re-brands are just a new set of token values.

Design ↔ code

Figma variables map straight to CSS custom properties — one shared language.

Related: see Color for building a palette and CSS Recipes for more reusable patterns.