Introduction
CSS Custom Properties (often called CSS variables) let you define reusable values directly in your stylesheets. Unlike preprocessor variables from Sass or Less, custom properties are live — they cascade, inherit, and can be manipulated with JavaScript at runtime.
This makes them incredibly powerful for theming, component APIs, and responsive design. Instead of duplicating values, you define them once and reference them everywhere. When the value changes, every reference updates automatically.
If you're building any kind of design system or maintaining a non-trivial codebase, custom properties aren't optional — they're essential infrastructure.
Key Concepts
Defining and Using Variables
:root {
--color-primary: #3b82f6;
--spacing-md: 1rem;
--font-body: 'Inter', system-ui, sans-serif;
}
.button {
background: var(--color-primary);
padding: var(--spacing-md);
font-family: var(--font-body);
}
Inheritance and Scope
.card {
--card-padding: 1.5rem;
padding: var(--card-padding);
}
.card--compact {
--card-padding: 0.75rem;
}
Fallback Values
.element {
color: var(--text-color, #333);
gap: var(--custom-gap, var(--spacing-md, 1rem));
}
Practical Examples
1. Dark Mode Theming
:root { --bg: #fff; --text: #1a1a1a; }
[data-theme="dark"] { --bg: #0a0a0a; --text: #e5e5e5; }
body { background: var(--bg); color: var(--text); }
2. Component API
.avatar {
--avatar-size: 3rem;
width: var(--avatar-size);
height: var(--avatar-size);
border-radius: 50%;
}
.avatar--sm { --avatar-size: 2rem; }
.avatar--lg { --avatar-size: 5rem; }
3. Responsive Spacing
:root { --spacing-unit: 0.5rem; }
@media (min-width: 768px) { :root { --spacing-unit: 0.75rem; } }
.stack > * + * { margin-top: calc(var(--spacing-unit) * 3); }
4. JavaScript Integration
const primary = getComputedStyle(document.documentElement)
.getPropertyValue('--color-primary');
document.documentElement.style
.setProperty('--color-primary', '#ef4444');
Best Practices
- Define design tokens as custom properties on :root for global access.
- Use semantic names (--color-primary) over descriptive names (--blue-500).
- Scope component-specific variables to the component selector, not :root.
- Always provide fallback values when using variables that might not be defined.
- Use calc() with custom properties for derived values.
- Avoid deeply nesting var() calls — hurts readability and debugging.
Common Pitfalls
- Custom properties are case-sensitive: --Color and --color are different.
- Custom properties don't work in media query conditions.
- An invalid var() value uses the inherited value, not the fallback.
- Over-abstracting: not every value needs to be a variable.
Browser Support
Excellent support: Chrome 49+, Firefox 31+, Safari 9.1+, Edge 15+. Safe for production. Only IE11 lacks support.
Related Guides
- CSS Cascade Layers — how custom properties interact with the cascade
- CSS Color Functions — combine variables with oklch and color-mix
- CSS clamp() and Fluid Design — use variables with fluid sizing