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.
<div class="theme"><button class="btn">Inherits theme</button><div class="theme theme--alt"><button class="btn">Alt theme</button></div></div>.theme{--accent:#2563eb}
.theme--alt{--accent:#db2777}
.btn{background:var(--accent);color:#fff;border:0;padding:9px 16px;border-radius:8px;margin:4px}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.
[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.
<button class="btn">Semantic token</button>:root{--blue-600:#2563eb;--accent:var(--blue-600)}
.btn{background:var(--accent);color:#fff;border:0;padding:10px 16px;border-radius:8px}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.
.btn { background: #2563eb; }.btn { background: var(--accent); }Hard-coding primitive colours in components, so re-theming means editing every component. Use semantic tokens that components reference.
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.
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.
1. Why use semantic tokens in components?
2. How does a scoped variable behave?
3. Can JavaScript change a variable at runtime?
- 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.