Custom Properties for Themes & Dynamic UI
Lesson 31 โข Advanced Track
What You'll Learn
๐ก Think of It Like This
Custom properties are like a control panel in a car. Instead of rewiring the engine to change speed, you turn a knob. Scoped properties are like having separate climate controls for front and back seats โ independent settings within the same vehicle. HSL-based variables are like having a single colour wheel that generates your entire palette from one hue value.
Understanding Dynamic Theming
CSS custom properties (variables) are not just static value holders โ they're live. When you change a variable's value via JavaScript (element.style.setProperty('--hue', '300')), every CSS rule referencing that variable updates instantly. This makes them the foundation for interactive themes.
The HSL colour model is ideal for theming because you can derive an entire colour scheme from a single hue number. By changing --hue alone, you get matching primary, light, and dark variants automatically.
Key Custom Property Patterns
| Pattern | Example | Use Case |
|---|---|---|
| Global tokens | :root { --primary: blue } | Site-wide design tokens |
| Scoped overrides | .alert { --color: red } | Component variants |
| JS manipulation | style.setProperty() | Runtime theme changes |
| Fallback values | var(--x, fallback) | Safety when var is undefined |
| Responsive vars | @media { :root { --cols: 1 } } | Responsive without per-property overrides |
1. Dynamic Hue & Radius Controls
<!DOCTYPE html>
<html><head><style>
:root { --hue: 210; --saturation: 80%; --primary: hsl(var(--hue), var(--saturation), 45%); --primary-light: hsl(var(--hue), var(--saturation), 92%); --primary-dark: hsl(var(--hue), var(--saturation), 30%); --radius: 8px; --spacing: 16px; }
body { font-family: system-ui, sans-serif; padding: 24px; background: #fafafa; }
.controls { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 24px; align-items: center; }
.controls label { font-weight: 600; font-siz
...2. Scoped Variables for Components
<!DOCTYPE html>
<html><head><style>
body { font-family: system-ui, sans-serif; padding: 24px; }
.alert { --alert-bg: #E3F2FD; --alert-border: #1976D2; --alert-text: #0D47A1; background: var(--alert-bg); border-left: 4px solid var(--alert-border); color: var(--alert-text); padding: 16px; border-radius: 0 8px 8px 0; margin: 12px 0; }
.alert.success { --alert-bg: #E8F5E9; --alert-border: #4CAF50; --alert-text: #1B5E20; }
.alert.warning { --alert-bg: #FFF3E0; --alert-border: #FF9800; --alert-text: #
... Scoping explained: When you define --alert-bg inside .alert, the variable only exists within that element and its children. A modifier class (.alert.success) overrides those same variables, creating a variant without any new properties โ just new values.
3. Multi-Theme Switcher
<!DOCTYPE html>
<html><head><style>
:root, [data-theme="ocean"] { --bg: #E3F2FD; --surface: #fff; --primary: #1565C0; --text: #0D47A1; --accent: #00BCD4; }
[data-theme="forest"] { --bg: #E8F5E9; --surface: #fff; --primary: #2E7D32; --text: #1B5E20; --accent: #8BC34A; }
[data-theme="sunset"] { --bg: #FFF3E0; --surface: #fff; --primary: #E65100; --text: #BF360C; --accent: #FF5722; }
[data-theme="midnight"] { --bg: #1a1a2e; --surface: #16213e; --primary: #64B5F6; --text: #e0e0e0; --accent: #AB47BC;
...4. Responsive Variables in Media Queries
<!DOCTYPE html>
<html><head><style>
:root { --gap: 16px; --cols: 3; --font-body: 1rem; --padding: 24px; }
@media (max-width: 768px) { :root { --gap: 12px; --cols: 2; --font-body: 0.95rem; --padding: 16px; } }
@media (max-width: 480px) { :root { --gap: 8px; --cols: 1; --font-body: 0.9rem; --padding: 12px; } }
body { font-family: system-ui; padding: var(--padding); background: #fafafa; }
.grid { display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: var(--gap); }
.item { background:
...When to Use This
- Design systems: Define all tokens (colours, spacing, radii) as variables for consistency
- Theme switchers: Light/dark mode, brand theming, user preferences
- Component libraries: Scoped variables let consumers customise without !important
- Interactive demos: Sliders, colour pickers that update UI in real time
โ ๏ธ Common Mistakes
- Forgetting fallback values โ Always provide:
var(--color, blue). If the variable is undefined, the fallback prevents breakage. - Overusing :root โ Scope variables to components when they're only needed there. Global scope pollutes the namespace.
- Using custom properties for static values โ If a value never changes and isn't part of a theme, a regular property is simpler.
- Not using HSL for colour systems โ Hex codes make deriving light/dark variants impossible. HSL lets you mathematically generate shades.
- Nesting var() inside calc() incorrectly โ
calc(var(--x) + 10px)works, butcalc(var(--x) + 10)fails if--xhas units. - Expecting animations on variables โ CSS variables can't be transitioned directly (yet). Use
@propertyregistration for animatable custom properties.
๐ Lesson Complete
- โ
JavaScript can update custom properties in real time via
style.setProperty() - โ HSL + custom properties = flexible, math-friendly colour systems
- โ Scoped variables create component variants without extra classes
- โ
[data-theme]selectors enable multi-theme switching - โ Variables in media queries simplify responsive design
- โ Fallbacks prevent breakage when variables are undefined
- โ
@propertyenables animating custom properties - โ Custom properties are the foundation of modern CSS architecture
Sign up for free to track which lessons you've completed and get learning reminders.