Introduction
CSS Custom Properties, commonly known as CSS Variables, revolutionize how we write and maintain CSS. Unlike preprocessor variables (Sass/Less), CSS custom properties are live values that can be changed at runtime, enabling dynamic theming, responsive design patterns, and more maintainable code.
They bridge the gap between static CSS and JavaScript, allowing for truly dynamic styling without the need for complex JavaScript DOM manipulations.
Basic Syntax and Usage
Declaring Custom Properties
Custom properties are declared using the --
prefix and can be defined at any level:
:root {
/* Global custom properties */
--primary-color: #3498db;
--secondary-color: #2ecc71;
--font-size-base: 16px;
--spacing-unit: 1rem;
--border-radius: 4px;
}
.card {
/* Local custom properties */
--card-padding: 1.5rem;
--card-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: var(--card-padding);
box-shadow: var(--card-shadow);
border-radius: var(--border-radius);
}
Using Custom Properties
.button {
background-color: var(--primary-color);
color: white;
padding: var(--spacing-unit);
border-radius: var(--border-radius);
border: none;
font-size: var(--font-size-base);
}
/* With fallback values */
.button--secondary {
background-color: var(--secondary-color, #gray); /* Falls back to gray */
color: var(--text-color, black);
}
Primary Button
Secondary Button
Dynamic Theming
Light/Dark Mode Toggle
:root {
/* Light theme (default) */
--bg-color: #ffffff;
--text-color: #333333;
--card-bg: #f8f9fa;
--border-color: #e1e5e9;
}
[data-theme="dark"] {
/* Dark theme */
--bg-color: #1a1a1a;
--text-color: #ffffff;
--card-bg: #2d2d2d;
--border-color: #404040;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
.card {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
padding: 1.5rem;
border-radius: 8px;
}
// Theme toggle functionality
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}
// Load saved theme
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
}
Card Component
This card would automatically adapt to light/dark themes using CSS custom properties.
JavaScript Integration
Updating Variables Dynamically
// Get custom property value
const primaryColor = getComputedStyle(document.documentElement)
.getPropertyValue('--primary-color');
// Set custom property value
document.documentElement.style.setProperty('--primary-color', '#e74c3c');
// Update multiple properties
function updateThemeColors(colors) {
Object.entries(colors).forEach(([property, value]) => {
document.documentElement.style.setProperty(`--${property}`, value);
});
}
updateThemeColors({
'primary-color': '#9b59b6',
'secondary-color': '#f39c12',
'accent-color': '#1abc9c'
});
Responsive Custom Properties
:root {
--container-padding: 1rem;
--font-size-h1: 2rem;
--grid-columns: 1;
}
@media (min-width: 768px) {
:root {
--container-padding: 2rem;
--font-size-h1: 2.5rem;
--grid-columns: 2;
}
}
@media (min-width: 1200px) {
:root {
--container-padding: 3rem;
--font-size-h1: 3rem;
--grid-columns: 3;
}
}
.container {
padding: 0 var(--container-padding);
}
h1 {
font-size: var(--font-size-h1);
}
.grid {
display: grid;
grid-template-columns: repeat(var(--grid-columns), 1fr);
gap: 1rem;
}
Advanced Patterns
1. Component-Level Theming
/* Button component with customizable properties */
.button {
/* Component defaults */
--button-bg: #3498db;
--button-color: white;
--button-padding: 0.75rem 1.5rem;
--button-border-radius: 4px;
--button-font-size: 1rem;
--button-hover-bg: #2980b9;
background-color: var(--button-bg);
color: var(--button-color);
padding: var(--button-padding);
border-radius: var(--button-border-radius);
font-size: var(--button-font-size);
border: none;
cursor: pointer;
transition: background-color 0.2s ease;
}
.button:hover {
background-color: var(--button-hover-bg);
}
/* Variant overrides */
.button--large {
--button-padding: 1rem 2rem;
--button-font-size: 1.125rem;
}
.button--danger {
--button-bg: #e74c3c;
--button-hover-bg: #c0392b;
}
.button--ghost {
--button-bg: transparent;
--button-color: var(--primary-color);
--button-hover-bg: rgba(52, 152, 219, 0.1);
border: 1px solid var(--primary-color);
}
2. Animation with Custom Properties
.progress-bar {
--progress-width: 0%;
--progress-color: #3498db;
--progress-bg: #ecf0f1;
--progress-height: 8px;
--progress-border-radius: 4px;
--progress-duration: 0.3s;
width: 100%;
height: var(--progress-height);
background-color: var(--progress-bg);
border-radius: var(--progress-border-radius);
overflow: hidden;
}
.progress-bar::before {
content: '';
display: block;
height: 100%;
width: var(--progress-width);
background-color: var(--progress-color);
transition: width var(--progress-duration) ease;
}
/* JavaScript to update progress */
.progress-bar[data-progress="25"] { --progress-width: 25%; }
.progress-bar[data-progress="50"] { --progress-width: 50%; }
.progress-bar[data-progress="75"] { --progress-width: 75%; }
.progress-bar[data-progress="100"] { --progress-width: 100%; }
Progress: 65%
3. CSS Grid with Custom Properties
.dynamic-grid {
--grid-min-width: 250px;
--grid-gap: 1rem;
--grid-max-columns: 4;
display: grid;
grid-template-columns: repeat(
auto-fit,
minmax(var(--grid-min-width), 1fr)
);
gap: var(--grid-gap);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.dynamic-grid {
--grid-min-width: 200px;
--grid-gap: 0.5rem;
}
}
@media (max-width: 480px) {
.dynamic-grid {
--grid-min-width: 100%;
--grid-gap: 1rem;
}
}
Real-World Examples
1. Design System Implementation
:root {
/* Color palette */
--color-primary-50: #eff6ff;
--color-primary-100: #dbeafe;
--color-primary-500: #3b82f6;
--color-primary-600: #2563eb;
--color-primary-900: #1e3a8a;
/* Typography scale */
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
/* Spacing scale */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
/* Component using design tokens */
.alert {
--alert-bg: var(--color-primary-50);
--alert-border: var(--color-primary-200);
--alert-text: var(--color-primary-900);
background-color: var(--alert-bg);
border: 1px solid var(--alert-border);
color: var(--alert-text);
padding: var(--space-4);
border-radius: var(--border-radius);
box-shadow: var(--shadow-sm);
}
2. Utility Classes with Variables
/* Spacing utilities */
.p-1 { padding: var(--space-1); }
.p-2 { padding: var(--space-2); }
.p-4 { padding: var(--space-4); }
.m-1 { margin: var(--space-1); }
.m-2 { margin: var(--space-2); }
.m-4 { margin: var(--space-4); }
/* Color utilities */
.text-primary { color: var(--color-primary-600); }
.text-secondary { color: var(--color-gray-600); }
.bg-primary { background-color: var(--color-primary-600); }
/* Size utilities */
.text-sm { font-size: var(--font-size-sm); }
.text-lg { font-size: var(--font-size-lg); }
.text-xl { font-size: var(--font-size-xl); }
Performance Considerations
1. Inheritance and Cascading
/* Custom properties inherit naturally */
.parent {
--text-color: #333;
--font-size: 16px;
}
.child {
color: var(--text-color); /* Inherits from parent */
font-size: var(--font-size); /* Inherits from parent */
}
/* Override at component level */
.special-section {
--text-color: #666; /* Only affects this section and descendants */
}
2. Avoiding Repaints
/* Good - Use transform for animations */
.animated-element {
--scale: 1;
transform: scale(var(--scale));
transition: transform 0.3s ease;
}
.animated-element:hover {
--scale: 1.1;
}
/* Avoid - Causes layout recalculations */
.bad-animation {
--width: 100px;
width: var(--width);
transition: width 0.3s ease;
}
Browser Support and Fallbacks
/* Progressive enhancement with fallbacks */
.button {
/* Fallback for older browsers */
background-color: #3498db;
color: white;
/* Enhanced with custom properties */
background-color: var(--primary-color, #3498db);
color: var(--button-text-color, white);
}
/* Feature detection */
@supports (--css: variables) {
.modern-component {
/* Use custom properties freely */
background: var(--gradient-bg);
border: var(--border-width) solid var(--border-color);
}
}
Debugging Custom Properties
/* Debug custom properties in DevTools */
:root {
--debug-mode: 1; /* Set to 0 to disable */
}
.debug-element {
position: relative;
}
.debug-element::after {
content: 'Primary: ' var(--primary-color) ' | Font: ' var(--font-size-base);
position: absolute;
top: 100%;
left: 0;
background: black;
color: white;
padding: 0.25rem;
font-size: 0.75rem;
white-space: nowrap;
display: var(--debug-mode, none);
}
Best Practices
1. Naming Conventions
:root {
/* Good naming - semantic and hierarchical */
--color-primary: #3498db;
--color-primary-dark: #2980b9;
--color-primary-light: #5dade2;
--font-family-primary: 'Inter', sans-serif;
--font-family-monospace: 'Fira Code', monospace;
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
/* Component-specific */
--button-padding-x: 1rem;
--button-padding-y: 0.5rem;
--card-border-radius: 8px;
}
2. Organization Strategies
:root {
/* 1. Color palette */
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
/* ... */
/* 2. Typography */
--font-family-sans: ui-sans-serif, system-ui;
--font-weight-normal: 400;
/* ... */
/* 3. Layout */
--container-max-width: 1200px;
--header-height: 64px;
/* ... */
/* 4. Components */
--button-height: 44px;
--input-border-color: var(--gray-300);
/* ... */
}
Conclusion
CSS Custom Properties are a game-changer for modern CSS development. They enable:
- Dynamic theming without JavaScript DOM manipulation
- Maintainable code through centralized value management
- Component-based styling with local property overrides
- Responsive design with breakpoint-specific values
- JavaScript integration for runtime customization
Key benefits include:
- ✅ Live values that update in real-time
- ✅ Natural inheritance following CSS cascade rules
- ✅ No build step required unlike preprocessor variables
- ✅ Excellent browser support (IE11+ with PostCSS polyfill)
- ✅ Debugging-friendly with DevTools integration
Start incorporating CSS Custom Properties into your projects today. Begin with simple color and spacing variables, then gradually expand to more complex patterns like component theming and responsive design systems. Your CSS will become more maintainable, flexible, and powerful.