CSS Custom Properties (Variables): A Complete Guide
CSS Custom Properties (commonly called CSS Variables) are one of the most powerful features in modern CSS. They allow you to define reusable values, build design systems, and create dynamic themes — all without a preprocessor.
What Are CSS Custom Properties?
Unlike Sass or Less variables (which are preprocessed), CSS custom properties are live in the browser. They're part of the CSS cascade and can be changed at runtime with JavaScript.
/* Define a custom property */
:root {
--color-primary: #6366f1;
--font-size-base: 16px;
--spacing-md: 1rem;
}
/* Use it anywhere */
.button {
background: var(--color-primary);
font-size: var(--font-size-base);
padding: var(--spacing-md);
}
Syntax Rules
/* Declaration: always starts with -- */
--my-variable: value;
/* Usage: var() function */
property: var(--my-variable);
/* Fallback value */
property: var(--undefined-var, #ff0000);
/* Nested fallbacks */
property: var(--a, var(--b, var(--c, default)));
Scope and Cascade
Custom properties respect the CSS cascade. Define them globally on :root or scope them to specific elements:
:root {
--gap: 1rem; /* Available everywhere */
}
.card {
--gap: 0.5rem; /* Overrides inside .card */
gap: var(--gap);
}
.card__header {
gap: var(--gap); /* Gets 0.5rem from parent .card */
}
Building a Design System
The real power of CSS variables is creating a consistent design system:
:root {
/* Colors */
--color-primary: #6366f1;
--color-primary-dk: #4f46e5;
--color-secondary: #ec4899;
--color-success: #22c55e;
--color-warning: #f59e0b;
--color-error: #ef4444;
/* Typography */
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
/* Spacing (8px base) */
--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 */
/* Borders */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 3px rgba(0,0,0,.12);
--shadow-md: 0 4px 12px rgba(0,0,0,.15);
--shadow-lg: 0 20px 60px rgba(0,0,0,.25);
/* Transitions */
--transition-fast: 150ms ease;
--transition-base: 250ms ease;
}
Dark Mode Theming
This is where CSS variables truly shine. No class toggling across dozens of components:
:root {
--bg: #ffffff;
--bg-2: #f8fafc;
--text: #0f172a;
--text-muted: #64748b;
--border: #e2e8f0;
}
[data-theme="dark"] {
--bg: #0d1117;
--bg-2: #161b22;
--text: #e6edf3;
--text-muted: #8b949e;
--border: #30363d;
}
// Toggle with JavaScript
const toggle = () => {
const html = document.documentElement
const current = html.getAttribute('data-theme')
html.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark')
}
Your components never need to change — just the variable values update:
.card {
background: var(--bg-2); /* Automatically works in both modes */
color: var(--text);
border: 1px solid var(--border);
}
JavaScript Integration
CSS custom properties are fully accessible from JavaScript:
// Read a variable
const root = document.documentElement
const primary = getComputedStyle(root).getPropertyValue('--color-primary').trim()
// → "#6366f1"
// Set a variable (affects all elements using it)
root.style.setProperty('--color-primary', '#10b981')
// Set on a specific element
const card = document.querySelector('.card')
card.style.setProperty('--gap', '2rem')
// Remove an override (falls back to :root value)
card.style.removeProperty('--gap')
Practical Patterns
Component Theming
Let a parent control a component's appearance:
.alert {
--alert-bg: #fef3c7;
--alert-border: #f59e0b;
--alert-text: #92400e;
background: var(--alert-bg);
border-left: 4px solid var(--alert-border);
color: var(--alert-text);
}
.alert--error {
--alert-bg: #fee2e2;
--alert-border: #ef4444;
--alert-text: #991b1b;
}
.alert--success {
--alert-bg: #dcfce7;
--alert-border: #22c55e;
--alert-text: #166534;
}
Responsive Typography
:root {
--font-size: clamp(14px, 1vw + 12px, 18px);
}
body {
font-size: var(--font-size);
}
Animation with Variables
@keyframes pulse {
0%, 100% { transform: scale(var(--scale-start, 1)); }
50% { transform: scale(var(--scale-end, 1.05)); }
}
.badge { animation: pulse 2s infinite; }
.badge--alert { --scale-end: 1.15; }
Calculating Derived Values
:root {
--columns: 3;
--gap: 1rem;
}
.grid {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
gap: var(--gap);
}
/* Override for mobile */
@media (max-width: 640px) {
:root { --columns: 1; }
}
CSS Variables vs Preprocessor Variables
| Feature | CSS Variables | Sass/Less Variables |
|---|---|---|
| Runtime changeable | ✅ Yes | ❌ No (compiled out) |
| JavaScript accessible | ✅ Yes | ❌ No |
| Cascade/inheritance | ✅ Yes | ❌ No |
| Browser support | ✅ All modern | ✅ After compilation |
| Dev tools visibility | ✅ Yes | ❌ No |
| Build step required | ❌ No | ✅ Yes |
Browser Support
CSS custom properties are supported in all modern browsers (Chrome 49+, Firefox 31+, Safari 9.1+, Edge 15+). For IE11 support, you'll need a PostCSS plugin — but in 2026, that's rarely a concern.
Tips & Gotchas
- Custom properties ARE case-sensitive:
--Color≠--color - They can hold any value — including spaces:
--font: 'JetBrains Mono', monospace - They participate in the cascade —
!importantapplies normally - Invalid at computed value time doesn't throw an error — it uses the property's initial value
- Use
@propertyfor typed variables (Chrome 85+):
@property --hue {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
Tools to Help You
Use our Color Converter to convert between HEX, RGB, and HSL when defining your color variables. The CSS Minifier can compress your final stylesheet, and the Gradient Generator helps create beautiful gradient values for your custom properties.
CSS custom properties are a cornerstone of modern CSS architecture. Once you start using them for theming, you'll never go back to hardcoded values.

