BiTree
  • Search For Lessons
  • Curriculum
  • Pricing
  • For Educators
  • Become a Tutor
  • About
  • Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
BiTree

Live coding lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Search For Lessons
  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Become a Tutor
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 BiTree. All rights reserved.
Curriculum/Web Development/CSS Custom Properties and Design Tokens
35 minBeginner

CSS Custom Properties and Design Tokens

After this lesson, you will be able to: Define and use CSS custom properties (variables) to build a small design-token system, swap themes (including dark mode) in five lines, and recognize when tokens belong in a project vs when they don't.

Every professional design system runs on tokens — named values for color, spacing, typography, and radii that the whole UI references instead of hardcoding raw values. Tokens live as CSS custom properties (a feature shipped to every browser since 2017, no preprocessing required). This lesson takes you from never having seen `--main-color: ...` to building a real token system and theming an entire app with a single attribute swap.

Prerequisites:CSS Fundamentals

What CSS variables actually are

A CSS custom property is a name starting with `--` that holds a value. You set it inside a selector (most often `:root`, which applies it globally), and you read it elsewhere with `var(--name)`. Unlike Sass variables, CSS variables exist at runtime — you can change them with JavaScript, override them inside a component scope, or swap them based on an attribute. That's what makes them the foundation of every modern theming system.

Defining and using variables

Three things to notice: the `--` prefix, the global scope on `:root`, and the `var(--name, fallback)` syntax for safe fallbacks.

css
:root {
--color-brand: #FF4848;
--color-text: #0f172a;
--color-bg: #ffffff;
--space-md: 1rem;
--radius-lg: 0.5rem;
}
.button {
background: var(--color-brand);
color: var(--color-bg);
padding: var(--space-md);
border-radius: var(--radius-lg);
/* Fallback if the var is unset */
border: 1px solid var(--color-border, transparent);
}

Why design tokens exist

Design tokens solve three problems hardcoded values can't. First, **consistency** — every "primary" button in the app uses `--color-brand` and updates simultaneously when the brand changes. Second, **handoff** — designers define tokens in Figma, engineers consume the same names in code, and the meaning doesn't drift. Third, **theming** — dark mode, accessibility high-contrast mode, and per-customer brand colors all become an attribute swap instead of a CSS rewrite. Tokens aren't for every value. They're for the cross-cutting decisions a design system needs to enforce: colors, spacing scale, typography scale, radius scale, shadow scale.

A small token system

Five categories cover most needs. Notice the naming: semantic (`--color-text-muted`) is more durable than literal (`--color-gray-600`).

tsx
:root {
/* color, semantic naming */
--color-bg: #ffffff;
--color-bg-muted: #f8fafc;
--color-text: #0f172a;
--color-text-muted: #64748b;
--color-brand: #FF4848;
--color-brand-hover: #e03e3e;
--color-border: #e2e8f0;
/* spacing scale, 4px base */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-4: 1rem; /* 16px */
--space-8: 2rem; /* 32px */
/* typography */
--font-sm: 0.875rem;
--font-base: 1rem;
--font-lg: 1.125rem;
--font-xl: 1.5rem;
/* radii */
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--radius-xl: 0.75rem;
/* shadows */
--shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
}

Theming with tokens (dark mode in five lines)

Because variables are runtime values, you can re-declare them inside any selector and the new values cascade everywhere they're used. The standard pattern is to use a `data-theme` attribute on the `<html>` or `<body>` element. Switching the attribute via JS swaps every token at once — every component, every page, instantly themed. No CSS file rewrite, no component changes, no rebuild.

Dark mode via [data-theme]

Declare the dark overrides under the attribute selector. The default declarations on `:root` are the light theme. Toggling `<html data-theme="dark">` swaps every token.

tsx
:root {
--color-bg: #ffffff;
--color-text: #0f172a;
--color-brand: #FF4848;
}
[data-theme="dark"] {
--color-bg: #0a1628;
--color-text: #f1f5f9;
--color-brand: #FF6464;
}
body {
background: var(--color-bg);
color: var(--color-text);
/* No theme-aware logic here — the variable handles it. */
}

Tokens inside Tailwind

Tailwind reads its color palette from `tailwind.config.js`. To make your CSS-variable tokens available as Tailwind utilities (so `bg-brand` works), extend the config to point at the variables instead of hardcoding values. This gives you the best of both: Tailwind's class ergonomics in markup, and a single CSS-variable source of truth that can be swapped at runtime. `theme.extend.colors.brand = 'var(--color-brand)'` is the one-line wire-up.

Refactor a hardcoded palette into tokens

The starter has the same color literal `#FF4848` repeated in three places and `#0f172a` in two. Define them once on `:root` as `--color-brand` and `--color-text`, then update every selector to use `var(--…)`. Required patterns below check for the variable definitions on `:root` and at least two `var(--color-brand)` usages.

Loading exercise…
Quick Check

Which value does NOT belong in a token?

Tokens are for cross-cutting design decisions. Pick the one that's too specific.

💡 Common mistakes only experienced devs catch

Five trap patterns. (1) Literal names like `--color-red-500` everywhere — when the brand changes from red to blue, you're stuck. Use semantic names like `--color-brand`. (2) Every value as a token — `--padding-card-top` is too narrow. Tokens are for cross-cutting choices, not one-off values. (3) Variables defined on `body` instead of `:root` — `:root` (which equals `<html>`) is the canonical place; some tools assume it. (4) Hardcoded fallbacks that override the token — `var(--color-brand, #FF4848)` keeps working forever even after you change the brand to blue. Fallbacks should be empty (`var(--color-brand)`) or to another token. (5) Forgetting `var(--…)` inside `calc()` requires no special syntax: `calc(var(--space-4) * 2)` is fine; people often try `calc(var(--space-4)*2)` and assume it broke when it didn't.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Tailwind CSS: The Utility-First Mental Model
Back to Web Development
Responsive Design with Tailwind Breakpoints→