Introduction to CSS Variables
CSS Variables, officially called Custom Properties, are entities defined by developers that contain specific values to be reused throughout a document. They help create more maintainable, themeable, and dynamic CSS by allowing you to define values once and reference them throughout your stylesheets. Unlike preprocessor variables (like Sass or Less), CSS Variables are part of the DOM, can be manipulated with JavaScript, and can be cascaded and inherited, making them powerful for responsive design, theming, and component-based architectures.
Core Concepts of CSS Variables
Basic Syntax
CSS Variables have two essential parts:
Declaration: Define a variable using the double hyphen prefix (
--) within a selector scope::root { --main-color: #3498db; }Usage: Reference a variable using the
var()function:.button { background-color: var(--main-color); }
Scope and Inheritance
CSS Variables follow the standard CSS cascading and inheritance rules:
- Variables defined in
:rootare globally available - Variables defined in specific selectors are scoped to those elements and their descendants
- Variables are inherited by child elements
- Variables can be redefined in any selector to override inherited values
:root {
--text-color: black;
}
.container {
--text-color: blue; /* Overrides for .container and its children */
}
p {
color: var(--text-color); /* Will be black or blue depending on context */
}
The var() Function
The var() function has two parameters:
var(--custom-property-name, fallback-value)
- First parameter: The custom property name (required)
- Second parameter: A fallback value (optional) used if the variable is not defined
.element {
color: var(--text-color, #333); /* Uses #333 if --text-color is not defined */
}
Step-by-Step Process for Implementing CSS Variables
1. Plan Your Variable System
Before coding, identify values that:
- Repeat throughout your CSS
- Might need to change together (theme colors, spacing units)
- Form logical groups (typography, colors, layout)
2. Define Global Variables
Start with global variables in the :root selector:
:root {
/* Colors */
--primary-color: #3498db;
--secondary-color: #2ecc71;
--text-color: #333;
--background-color: #f8f8f8;
/* Typography */
--font-family-base: 'Open Sans', sans-serif;
--font-size-base: 16px;
--line-height-base: 1.5;
/* Spacing */
--spacing-unit: 8px;
--spacing-small: calc(var(--spacing-unit) * 1);
--spacing-medium: calc(var(--spacing-unit) * 2);
--spacing-large: calc(var(--spacing-unit) * 3);
/* Layout */
--container-width: 1200px;
--border-radius: 4px;
}
3. Implement Variables in Your CSS
Replace hardcoded values with variable references:
body {
font-family: var(--font-family-base);
font-size: var(--font-size-base);
line-height: var(--line-height-base);
color: var(--text-color);
background-color: var(--background-color);
}
.button {
background-color: var(--primary-color);
padding: var(--spacing-small) var(--spacing-medium);
border-radius: var(--border-radius);
}
4. Create Component-Specific Variables
Define scoped variables for components:
.card {
--card-padding: var(--spacing-medium);
--card-bg: white;
padding: var(--card-padding);
background-color: var(--card-bg);
border-radius: var(--border-radius);
}
.card.dark {
--card-bg: #222;
--text-color: white;
}
5. Add Dynamic Functionality with JavaScript
Manipulate variables with JavaScript for interactivity:
// Change theme color on button click
document.getElementById('theme-toggle').addEventListener('click', function() {
document.documentElement.style.setProperty('--primary-color', '#e74c3c');
});
// Get current value of a CSS variable
const currentColor = getComputedStyle(document.documentElement)
.getPropertyValue('--primary-color');
Key Techniques for Working with CSS Variables
1. Using Calculations with CSS Variables
Combine variables with calc() for dynamic sizing:
:root {
--header-height: 60px;
--footer-height: 80px;
}
.main-content {
min-height: calc(100vh - (var(--header-height) + var(--footer-height)));
}
2. Nested Variables
Reference variables within other variables:
:root {
--brand-hue: 220; /* Blue */
--brand-saturation: 70%;
--brand-lightness: 50%;
--primary-color: hsl(var(--brand-hue), var(--brand-saturation), var(--brand-lightness));
--primary-light: hsl(var(--brand-hue), var(--brand-saturation), 70%);
--primary-dark: hsl(var(--brand-hue), var(--brand-saturation), 30%);
}
3. Responsive Design with Media Queries
Adjust variables for different screen sizes:
:root {
--font-size-base: 16px;
--spacing-unit: 8px;
}
@media (max-width: 768px) {
:root {
--font-size-base: 14px;
--spacing-unit: 6px;
}
}
4. Creating Color Schemes
Build complete themes with variables:
:root {
/* Light theme (default) */
--bg-primary: #ffffff;
--bg-secondary: #f0f0f0;
--text-primary: #333333;
--text-secondary: #666666;
--accent-color: #3498db;
}
.dark-theme {
--bg-primary: #121212;
--bg-secondary: #1e1e1e;
--text-primary: #ffffff;
--text-secondary: #b0b0b0;
--accent-color: #29b6f6;
}
5. Animation with CSS Variables
Create dynamic animations:
.button {
--transition-speed: 0.3s;
transition: transform var(--transition-speed) ease;
}
.button:hover {
--hover-scale: 1.05;
transform: scale(var(--hover-scale));
}
.slow-button {
--transition-speed: 0.8s;
}
6. Working with Units
Combine CSS variables with different units:
:root {
--baseline: 24;
--font-size-heading: calc(var(--baseline) * 2px);
--width-percentage: 80;
--max-width: calc(var(--width-percentage) * 1%);
}
7. Creating Design Systems
Build comprehensive design systems with variables:
:root {
/* Typography Scale */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-md: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
/* Spacing Scale */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-12: 3rem; /* 48px */
}
Comparison Tables
CSS Variables vs. Preprocessor Variables
| Feature | CSS Variables | Sass/Less Variables |
|---|---|---|
| Runtime modification | Yes (with JavaScript) | No (compile-time only) |
| DOM access | Yes | No |
| Cascade & inheritance | Yes | No |
| Browser support | Modern browsers (IE11 needs polyfill) | All browsers (compiles to CSS) |
| Conditionally set values | Yes (with media queries) | Yes (with conditionals) |
| Performance | Slight runtime cost | No runtime cost |
| Scope | Set by CSS selector rules | Limited to file or block |
| Debugging | Visible in DevTools | Not visible after compilation |
CSS Variable Function Comparisons
| Function | Purpose | Example |
|---|---|---|
var() | Access a CSS variable | var(--primary-color) |
calc() with variables | Dynamic calculations | calc(var(--spacing) * 2) |
env() | Access environment variables | env(safe-area-inset-top) |
min() with variables | Take the smallest value | min(var(--width), 100%) |
max() with variables | Take the largest value | max(var(--min-width), 20rem) |
clamp() with variables | Value within a range | clamp(var(--min), var(--preferred), var(--max)) |
Common Challenges and Solutions
Challenge: Variable Not Working
Problem: Variable isn’t applied as expected.
Solutions:
- Check spelling and case (variables are case-sensitive)
- Verify scope (make sure the variable is defined in the current scope or a parent scope)
- Add a fallback value:
var(--color, black) - Check browser support and consider a polyfill for older browsers
Challenge: Cascading Complexity
Problem: Hard to track which variable definition applies in complex projects.
Solutions:
- Use descriptive naming conventions
- Document your variable system
- Use browser DevTools to inspect computed values
- Minimize variable redefinitions across different scopes
Challenge: Performance Concerns
Problem: Excessive use of variables might impact performance.
Solutions:
- Don’t create variables for single-use values
- Group related variables under common selectors
- Use critical variables in frequently accessed properties judiciously
- Test performance in your target browsers
Challenge: Using Variables in Media Queries
Problem: Variables can’t be used in media query conditions.
Solution:
/* Won't work: */
@media (max-width: var(--breakpoint)) { ... }
/* Solution: Change the variables inside media queries instead */
@media (max-width: 768px) {
:root {
--column-count: 2;
}
}
Challenge: IE11 Support
Problem: Limited support in older browsers like IE11.
Solutions:
- Use a polyfill like css-vars-ponyfill
- Provide fallbacks for critical properties
- Consider using a preprocessor alongside CSS variables
Best Practices and Tips
Naming Conventions
- Use descriptive, semantic names:
--primary-colornot--color1 - Group related variables with prefixes:
--font-size-small,--font-size-medium - Use kebab-case (lowercase with hyphens)
- Indicate units in variable names when helpful:
--spacing-small-px
Organization Tips
- Define global variables in
:root - Group variables by function (colors, typography, spacing, etc.)
- Create component-specific variables for encapsulation
- Document your variable system for team use
Performance Optimization
- Don’t create variables for values used only once
- Minimize deep nesting of variable references
- Use local scoping for component-specific variables
- Test performance in production-like environments
Advanced Techniques
Generating Shades and Tints:
:root { --color-base: 200, 100%, 50%; --color-light-10: hsl(var(--color-base) / 0.1); --color-light-25: hsl(var(--color-base) / 0.25); --color-dark-10: hsl(var(--color-base) / 0.9); --color-dark-25: hsl(var(--color-base) / 0.75); }Creating Property Maps:
.margin-top { --size: var(--spacing-medium); margin-top: var(--size); } .margin-top.small { --size: var(--spacing-small); }Feature Toggling:
:root { --feature-new-header: 1; /* 1 = on, 0 = off */ } .header-new { display: calc(var(--feature-new-header) * 1); /* 1 or 0 */ } .header-old { display: calc(1 - var(--feature-new-header)); /* 0 or 1 */ }Stateful Components:
.component { --active: 0; opacity: calc(0.5 + (var(--active) * 0.5)); } .component.is-active { --active: 1; }Container Queries Simulation:
.container { --container-width: 1000; } @media (max-width: 800px) { .container { --container-width: 800; } } .container .child { font-size: calc(12px + (var(--container-width) / 100)); }
JavaScript Integration with CSS Variables
Reading CSS Variables
// Get a variable from :root
const rootStyles = getComputedStyle(document.documentElement);
const primaryColor = rootStyles.getPropertyValue('--primary-color').trim();
// Get a variable from a specific element
const buttonStyles = getComputedStyle(document.querySelector('.button'));
const buttonPadding = buttonStyles.getPropertyValue('--button-padding').trim();
Writing CSS Variables
// Set a variable on :root (global)
document.documentElement.style.setProperty('--primary-color', '#e74c3c');
// Set a variable on a specific element (local)
const button = document.querySelector('.button');
button.style.setProperty('--button-padding', '20px');
Creating Theme Switchers
const darkModeToggle = document.getElementById('dark-mode-toggle');
darkModeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-theme');
});
// Or using variables directly:
const colorModeToggle = document.getElementById('color-mode-toggle');
colorModeToggle.addEventListener('click', () => {
if (document.documentElement.style.getPropertyValue('--bg-primary') === '#ffffff') {
document.documentElement.style.setProperty('--bg-primary', '#121212');
document.documentElement.style.setProperty('--text-primary', '#ffffff');
} else {
document.documentElement.style.setProperty('--bg-primary', '#ffffff');
document.documentElement.style.setProperty('--text-primary', '#333333');
}
});
Reactive Variables with ResizeObserver
// Update CSS variables based on element size
const container = document.querySelector('.container');
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const width = entry.contentRect.width;
entry.target.style.setProperty('--container-width', width + 'px');
entry.target.style.setProperty('--columns', Math.floor(width / 200));
}
});
observer.observe(container);
Browser Support and Fallbacks
Current Browser Support
CSS Variables are supported in all modern browsers including:
- Chrome 49+
- Firefox 31+
- Safari 9.1+
- Edge 16+
- Opera 36+
Internet Explorer 11 does not support CSS Variables.
Providing Fallbacks
Method 1: Direct Property Fallbacks
.button {
background-color: #3498db; /* Fallback */
background-color: var(--primary-color);
}
Method 2: Var() Fallbacks
.button {
background-color: var(--primary-color, #3498db);
}
Method 3: Feature Detection
@supports (--css: variables) {
/* Styles that use CSS variables */
.element {
color: var(--text-color);
}
}
@supports not (--css: variables) {
/* Fallback styles */
.element {
color: #333;
}
}
Method 4: JavaScript Polyfill
For IE11 support, use a polyfill:
<script src="https://cdn.jsdelivr.net/npm/css-vars-ponyfill@2"></script>
<script>
cssVars({
// Options
onlyLegacy: true,
watch: true
});
</script>
Resources for Further Learning
Documentation
Tutorials and Articles
- CSS-Tricks: A Complete Guide to Custom Properties
- Smashing Magazine: A Strategy Guide To CSS Custom Properties
- Web.dev: CSS Variables
Tools and Libraries
- PostCSS Custom Properties – Compile CSS variables for older browsers
- css-vars-ponyfill – Polyfill for legacy browsers
- Styled Components – CSS-in-JS library with support for theming via variables
CSS Variable Examples and Patterns
- Open Props – CSS custom properties for design systems
- Bootstrap v5 Custom Properties – Example of variables in a framework
- Color.js – Color manipulation library that works well with CSS variables
This cheatsheet covers the essential concepts, techniques, and use cases for CSS Variables (Custom Properties). By leveraging these powerful features, you can create more maintainable, dynamic, and flexible stylesheets for modern web development.
