</> ConvertAtlas Developer Tools
Level 3 · Advanced — Modern CSS Lesson 36 Advanced 13 min

CSS Custom Properties for Themes

Use variables to power multi-theme systems, runtime theming and scoped overrides.

You used variables for dark mode at the intermediate level. Now we go deeper: scoped overrides, multiple themes, runtime updates and layered tokens — the techniques that power real theming systems and design tokens.

Variables cascade and scope

Custom properties follow the cascade like any property. A variable set on an element applies to it and its descendants, and can be overridden lower down. This scoping is what makes component variants and nested themes possible.

HTML
<div class="theme"><button class="btn">Inherits theme</button><div class="theme theme--alt"><button class="btn">Alt theme</button></div></div>
CSS
.theme{--accent:#2563eb}
.theme--alt{--accent:#db2777}
.btn{background:var(--accent);color:#fff;border:0;padding:9px 16px;border-radius:8px;margin:4px}
Result
A nested scope overrides --accent for its subtree.

Multiple themes

Define theme variants by overriding tokens on a root attribute or class. Each theme only redeclares the variables that change, keeping themes tiny and consistent.

css
[data-theme="light"] { --bg:#fff; --text:#0f172a; }
[data-theme="dark"]  { --bg:#0f172a; --text:#f1f5f9; }
[data-theme="sepia"] { --bg:#f4ecd8; --text:#433422; }

Layered (semantic) tokens

Professional systems use two layers: primitive tokens (raw values like <code>--blue-600</code>) and semantic tokens that reference them (<code>--accent: var(--blue-600)</code>). Components use only semantic tokens, so re-theming means repointing semantics — never touching components. This is the heart of the senior-level design system.

HTML
<button class="btn">Semantic token</button>
CSS
:root{--blue-600:#2563eb;--accent:var(--blue-600)}
.btn{background:var(--accent);color:#fff;border:0;padding:10px 16px;border-radius:8px}
Result
Semantic --accent points at a primitive token.

Fallbacks and runtime updates

<code>var(--x, fallback)</code> guards against an unset variable. And because variables are live, JavaScript can update one (<code>element.style.setProperty('--accent', '#f00')</code>) to re-theme instantly — the basis of live theme pickers.

Common mistake
Don't
.btn { background: #2563eb; }
Do
.btn { background: var(--accent); }

Hard-coding primitive colours in components, so re-theming means editing every component. Use semantic tokens that components reference.

Best practice

Separate primitive tokens from semantic tokens, and let components use only semantic ones. Override semantics per theme on a root attribute. This makes any number of themes cheap and consistent.

Try it yourself

Create primitive tokens (--blue-600, --pink-600) and a semantic --accent. Build a button using --accent, then create a .theme--alt scope that repoints --accent to the pink primitive.

Quick quiz

1. Why use semantic tokens in components?

2. How does a scoped variable behave?

3. Can JavaScript change a variable at runtime?

Key takeaways
  • Variables cascade and scope — enabling nested themes and variants.
  • Define themes by overriding tokens on a root attribute/class.
  • Separate primitive and semantic tokens; components use semantics.
  • Variables are live — JavaScript can re-theme at runtime.