CSS Custom Properties Guide
Complete guide to CSS variables with practical examples.
Click any code block to copy to clipboard.
What are CSS Custom Properties?
CSS Custom Properties (also called CSS Variables) let you define reusable values in your stylesheets. Unlike preprocessor variables (Sass/Less), they are native to CSS, cascade like other properties, and can be modified with JavaScript at runtime.
Custom properties always start with two dashes (--) and are case-sensitive.
/* Defining a custom property */ --property-name: value; /* Using a custom property */ var(--property-name)
:root {
--primary-color: #3b82f6;
--spacing-md: 16px;
--font-sans: 'Inter', sans-serif;
}
.button {
background-color: var(--primary-color);
padding: var(--spacing-md);
font-family: var(--font-sans);
}Defining Variables
Global Variables (:root)
Define in :root for global access. The :root selector matches the document's root element (html), giving variables the highest scope.
:root {
/* Global variables - accessible everywhere */
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--color-success: #22c55e;
--color-error: #ef4444;
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.25rem;
}Scoped Variables
Define within a selector to limit scope. Scoped variables are only available to that element and its descendants.
.card {
/* Scoped to .card and its descendants */
--card-padding: 24px;
--card-radius: 12px;
--card-shadow: 0 4px 12px rgba(0,0,0,0.1);
padding: var(--card-padding);
border-radius: var(--card-radius);
box-shadow: var(--card-shadow);
}
.card.compact {
/* Override for compact variant */
--card-padding: 12px;
--card-radius: 8px;
}Naming Conventions
| Pattern | Example | Usage |
|---|---|---|
--color-* | --color-primary, --color-text-muted | Colors |
--spacing-* | --spacing-md, --spacing-lg | Spacing |
--font-* | --font-size-lg, --font-weight-bold | Typography |
--radius-* | --radius-sm, --radius-full | Border radius |
--shadow-* | --shadow-sm, --shadow-lg | Box shadows |
--z-* | --z-dropdown, --z-modal | Z-index layers |
Using Variables
var() Syntax
Use var(--property-name) to reference a custom property. Variables can be used anywhere a value is expected.
.element {
/* Basic usage */
color: var(--color-primary);
/* In shorthand properties */
padding: var(--spacing-sm) var(--spacing-md);
/* In calc() */
width: calc(100% - var(--spacing-lg));
/* Multiple variables */
box-shadow: var(--shadow-offset-x) var(--shadow-offset-y)
var(--shadow-blur) var(--shadow-color);
}Fallback Values
Provide a fallback value after a comma. Used when the variable is not defined or invalid.
.element {
/* Single fallback value */
color: var(--color-primary, #3b82f6);
/* Fallback to another variable */
color: var(--color-brand, var(--color-primary));
/* Nested fallbacks */
color: var(--color-brand, var(--color-primary, blue));
/* Fallback for complex values */
font-family: var(--font-custom, 'Helvetica Neue', sans-serif);
}Nested Fallbacks
Chain fallbacks by nesting var() functions. Useful for building flexible component systems.
/* Fallback chain example */
.button {
/* Try --btn-bg, then --color-accent, then hardcoded */
background: var(--btn-bg, var(--color-accent, #8b5cf6));
}
/* This is useful for component theming */
.alert {
--alert-bg: var(--color-warning, #f59e0b);
--alert-text: var(--color-warning-text, #78350f);
background: var(--alert-bg);
color: var(--alert-text);
}Inheritance & Cascade
Variables Inherit
Custom properties inherit down the DOM tree, just like color or font-family. Override them in child selectors to create scoped theming.
:root {
--text-color: #1f2937;
}
body {
color: var(--text-color); /* Uses #1f2937 */
}
.dark-section {
--text-color: #f9fafb; /* Override for this subtree */
}
.dark-section p {
color: var(--text-color); /* Uses #f9fafb */
}Scoped Theming Example
Define internal variables for a component, then override them for variants. This pattern creates flexible, maintainable components.
/* Base component styles */
.button {
--btn-bg: var(--color-primary);
--btn-text: white;
--btn-padding: 12px 24px;
background: var(--btn-bg);
color: var(--btn-text);
padding: var(--btn-padding);
}
/* Variant: override variables */
.button.secondary {
--btn-bg: var(--color-secondary);
}
.button.outline {
--btn-bg: transparent;
--btn-text: var(--color-primary);
border: 2px solid var(--color-primary);
}
.button.small {
--btn-padding: 8px 16px;
}Live Demo: Scoped Button Variables
Dynamic Values with JavaScript
Setting Variables
Use element.style.setProperty() to set custom properties at runtime.
// Set a CSS variable on an element
element.style.setProperty('--color-primary', '#ef4444');
// Set on document root (global)
document.documentElement.style.setProperty('--color-primary', '#ef4444');
// Remove a variable
element.style.removeProperty('--color-primary');Reading Variables
Use getComputedStyle() to read the computed value of a variable.
// Get computed value of a CSS variable
const styles = getComputedStyle(element);
const primaryColor = styles.getPropertyValue('--color-primary');
// Returns: '#3b82f6' (the computed value)
// Get from root
const rootStyles = getComputedStyle(document.documentElement);
const spacing = rootStyles.getPropertyValue('--spacing-md');Theme Switching Example
// Theme switching example
const themeToggle = document.getElementById('theme-toggle');
const root = document.documentElement;
themeToggle.addEventListener('click', () => {
const isDark = root.classList.toggle('dark');
if (isDark) {
root.style.setProperty('--color-bg', '#1f2937');
root.style.setProperty('--color-text', '#f9fafb');
root.style.setProperty('--color-primary', '#60a5fa');
} else {
root.style.setProperty('--color-bg', '#ffffff');
root.style.setProperty('--color-text', '#1f2937');
root.style.setProperty('--color-primary', '#3b82f6');
}
});React Integration
// React hook for CSS variables
function useCSSVariable(name: string, initialValue: string) {
const [value, setValue] = useState(initialValue);
useEffect(() => {
document.documentElement.style.setProperty(name, value);
}, [name, value]);
return [value, setValue] as const;
}
// Usage
function App() {
const [primaryColor, setPrimaryColor] = useCSSVariable(
'--color-primary',
'#3b82f6'
);
return (
<input
type="color"
value={primaryColor}
onChange={(e) => setPrimaryColor(e.target.value)}
/>
);
}Practical Use Cases
Color Theming System
:root {
/* Color system */
--color-primary: #3b82f6;
--color-primary-hover: #2563eb;
--color-primary-light: #dbeafe;
--color-gray-50: #f9fafb;
--color-gray-100: #f3f4f6;
--color-gray-200: #e5e7eb;
--color-gray-700: #374151;
--color-gray-900: #111827;
--color-bg: var(--color-gray-50);
--color-text: var(--color-gray-900);
--color-text-muted: var(--color-gray-700);
--color-border: var(--color-gray-200);
}Spacing Scale
:root {
/* Spacing scale (8px base) */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
}
.card {
padding: var(--space-6);
margin-bottom: var(--space-4);
gap: var(--space-3);
}Typography System
:root {
/* Font families */
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'Fira Code', monospace;
/* Font sizes */
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--text-4xl: 2.25rem;
/* Line heights */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
/* Font weights */
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
}Dark Mode Toggle
:root {
--color-bg: #ffffff;
--color-text: #1f2937;
--color-text-muted: #6b7280;
--color-border: #e5e7eb;
--color-surface: #f9fafb;
--color-primary: #3b82f6;
}
/* Dark mode override */
:root.dark,
[data-theme="dark"] {
--color-bg: #111827;
--color-text: #f9fafb;
--color-text-muted: #9ca3af;
--color-border: #374151;
--color-surface: #1f2937;
--color-primary: #60a5fa;
}
/* Or use media query */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #111827;
--color-text: #f9fafb;
/* ... */
}
}Component Variants
/* Component with internal variables */
.alert {
--alert-bg: var(--color-gray-100);
--alert-text: var(--color-gray-900);
--alert-border: var(--color-gray-300);
--alert-icon: var(--color-gray-500);
background: var(--alert-bg);
color: var(--alert-text);
border: 1px solid var(--alert-border);
padding: var(--space-4);
border-radius: 8px;
}
.alert-success {
--alert-bg: #dcfce7;
--alert-text: #166534;
--alert-border: #86efac;
--alert-icon: #22c55e;
}
.alert-error {
--alert-bg: #fee2e2;
--alert-text: #991b1b;
--alert-border: #fca5a5;
--alert-icon: #ef4444;
}
.alert-warning {
--alert-bg: #fef3c7;
--alert-text: #92400e;
--alert-border: #fcd34d;
--alert-icon: #f59e0b;
}CSS Variables vs Sass/Less Variables
| Feature | CSS Custom Properties | Sass/Less Variables |
|---|---|---|
| Syntax | --var-name: value; / var(--var-name) | $var-name: value; |
| Scope | Cascade & inherit through DOM | Lexical (file/block scope) |
| Runtime changes | Yes (via JS or media queries) | No (compiled at build time) |
| Browser support | Modern browsers (IE11+) | All (compiles to regular CSS) |
| DevTools inspection | Visible in computed styles | Variables replaced with values |
| Media query reactive | Yes | No (must duplicate code) |
| Fallback values | Built-in with var(--x, fallback) | Manual with @if or maps |
| Math operations | Requires calc() | Native math operators |
| Theming | Runtime theme switching | Build-time theme generation |
When to Use Each
:root {
/* Use CSS Custom Properties for: */
--color-primary: #3b82f6; /* Theming (runtime changes) */
--spacing-unit: 8px; /* Values that might change */
}
/* Use Sass variables for: */
$breakpoint-md: 768px; /* Build-time constants */
$grid-columns: 12; /* Config that never changes */
$z-layers: (
modal: 100,
dropdown: 50
); /* Complex data structures */
/* Best of both: Sass variables to CSS variables */
$brand-blue: #3b82f6;
:root {
--color-primary: #{$brand-blue};
}Key Takeaway
Use CSS Custom Properties for values that need to change at runtime (theming, dark mode, responsive adjustments). Use Sass/Less variables for build-time configuration, complex data structures, and mathematical operations. Many projects use both together for maximum flexibility.
Browser Support & Tips
Browser Support
- Chrome 49+ (March 2016)
- Firefox 31+ (July 2014)
- Safari 9.1+ (March 2016)
- Edge 15+ (April 2017)
- IE - Not supported (use fallbacks)
Pro Tips
- Always provide fallbacks for critical styles
- Use meaningful, descriptive names
- Group related variables with prefixes
- Document your variable system
- Keep scope as narrow as practical
Explore more CSS tools
CSS Grid Generator
Create grid layouts visually
CSS Flexbox Generator
Create flex layouts visually
CSS Gradient Generator
Create custom gradients
CSS Text Gradient
Gradient text effects
CSS Gradients Collection
275+ ready-to-use gradients
CSS Box Shadow Generator
Design multi-layer shadows
CSS Selection Generator
Style text highlight colors
CSS Scrollbar Generator
Style custom scrollbars
CSS Blend Mode Generator
Mix colors with blend modes
CSS Cursor Generator
Preview all cursor styles
CSS Loader Generator
Create loading animations
CSS Button Generator
Design buttons with hover effects
CSS Glow Generator
Create neon glow effects
Fluid Typography
Scale text across screen sizes
