CSS Custom Properties
Dynamic variables that revolutionize how you write CSS. Create flexible design systems, seamless theming, and maintainable stylesheets with native CSS variables.
What Are Custom Properties?
Custom properties (also called CSS variables) let you store values that you can reuse throughout your stylesheet. Unlike preprocessor variables (Sass, Less), these are native to CSS and live in the browser - which means you can change them dynamically with JavaScript!
/* Define custom properties */
:root {
--primary-color: #2563eb;
--spacing: 1rem;
--border-radius: 12px;
}
/* Use them with var() function */
.box {
background: var(--primary-color);
padding: var(--spacing);
border-radius: var(--border-radius);
}
/* Fallback values if property isn't defined */
.element {
color: var(--text-color, #000000); /* Falls back to black */
}
Syntax Breakdown
- Defining:
--property-name: value;- Custom properties always start with-- - Using:
var(--property-name)- Thevar()function retrieves the value - Fallback:
var(--property-name, fallback)- Comma-separated fallback if the property isn't set
Scope & Inheritance
Custom properties follow CSS cascade rules. Define them globally on :root, or scope them to specific elements. Child elements inherit their parent's custom properties.
/* Global scope - available everywhere */
:root {
--global-color: #2563eb;
}
/* Local scope - only for this element and children */
.card {
--card-padding: 2rem;
--card-bg: #f8fafc;
padding: var(--card-padding);
background: var(--card-bg);
}
/* Child inherits parent's custom properties */
.card .title {
/* Can use --card-padding and --card-bg here */
margin-bottom: calc(var(--card-padding) / 2);
}
Pro tip: Use :root for design system tokens (colors, spacing, typography), and local scope for component-specific values. This creates a clear hierarchy and makes your CSS more maintainable.
Theming Made Easy
Custom properties are perfect for theming. Change a few variables and your entire design transforms. This is how this very website implements its dark/light theme!
Light Theme
Clean and bright, perfect for daytime browsing.
Dark Theme
Easy on the eyes, great for nighttime coding.
/* Light theme (default) */
:root {
--bg-color: #ffffff;
--text-color: #1e293b;
--border-color: #e2e8f0;
}
/* Dark theme - just override the variables */
[data-theme="dark"] {
--bg-color: #0f172a;
--text-color: #f1f5f9;
--border-color: #334155;
}
/* Components use the variables */
.card {
background: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
Notice how we only change the custom property values - the component styles stay exactly the same. This is the magic of CSS variables!
Dynamic Updates with JavaScript
Unlike Sass variables that compile away, CSS custom properties exist at runtime. This means JavaScript can modify them on the fly, creating truly interactive experiences.
Dynamic Color
// Get the element
const element = document.querySelector('.color-preview');
// Set a custom property value
element.style.setProperty('--dynamic-color', 'hsl(220, 70%, 55%)');
// Read a custom property value
const color = getComputedStyle(element)
.getPropertyValue('--dynamic-color');
// Update based on user input
hueSlider.addEventListener('input', (e) => {
const hue = e.target.value;
element.style.setProperty('--hue', hue);
});
.color-preview {
--hue: 220;
--saturation: 70%;
--lightness: 55%;
background: hsl(var(--hue), var(--saturation), var(--lightness));
color: white;
}
Advanced Patterns
Custom properties unlock powerful techniques that were impossible before. Here are some advanced patterns that'll level up your CSS game.
Calculations with Custom Properties
:root {
--base-spacing: 0.5rem;
}
.spacing-item {
--multiplier: 1;
margin-bottom: calc(var(--base-spacing) * var(--multiplier));
padding: calc(var(--base-spacing) * var(--multiplier));
}
/* Override multiplier inline */
.spacing-item { --multiplier: 2; }
Responsive Design with Custom Properties
/* Responsive values change in media queries */
:root {
--container-width: 100%;
--font-size: 1rem;
--grid-columns: 1;
}
@media (min-width: 768px) {
:root {
--container-width: 90%;
--font-size: 1.125rem;
--grid-columns: 2;
}
}
@media (min-width: 1024px) {
:root {
--container-width: 1200px;
--font-size: 1.25rem;
--grid-columns: 3;
}
}
/* Components automatically adapt */
.container {
width: var(--container-width);
font-size: var(--font-size);
}
.grid {
display: grid;
grid-template-columns: repeat(var(--grid-columns), 1fr);
}
Component API Pattern
/* Component with customizable API */
.api-button {
/* Default values */
--button-color: #2563eb;
--button-size: 1;
background: var(--button-color);
padding: calc(0.75rem * var(--button-size)) calc(1.5rem * var(--button-size));
font-size: calc(1rem * var(--button-size));
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
/* Hover state lightens the custom color */
.api-button:hover {
filter: brightness(1.1);
}
<!-- Customize via inline styles -->
<button class="api-button" style="--button-color: #ef4444; --button-size: 1.2;">
Large Danger Button
</button>
This pattern creates a clean API for your components. Instead of creating tons of modifier classes (.button-large, .button-danger), you expose custom properties that users can adjust.
Browser Support
CSS custom properties have excellent browser support! They work in all modern browsers since 2016.
Browser Compatibility
- Chrome/Edge: 49+ ✅
- Firefox: 31+ ✅
- Safari: 9.1+ ✅
- Internet Explorer: Not supported ❌
For IE11 support (if you must), provide fallback values:
.element {
/* Fallback for browsers without custom property support */
color: #2563eb;
/* Custom property value (overrides fallback in modern browsers) */
color: var(--primary-color);
}
Real-World Example: Design System
Let's build a complete mini design system using custom properties. This is how professional design systems (like Material Design or Chakra UI) are structured.
:root {
/* Color Palette */
--color-primary-50: #eff6ff;
--color-primary-100: #dbeafe;
--color-primary-500: #3b82f6;
--color-primary-900: #1e3a8a;
/* Semantic Colors */
--color-background: var(--color-primary-50);
--color-text: var(--color-primary-900);
--color-accent: var(--color-primary-500);
/* Spacing Scale (8pt grid) */
--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 */
/* Typography Scale */
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
/* Border Radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
/* 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);
}
/* Components reference the design tokens */
.card {
background: var(--color-background);
color: var(--color-text);
padding: var(--space-6);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
}
.button {
background: var(--color-accent);
color: white;
padding: var(--space-3) var(--space-6);
font-size: var(--font-size-base);
border-radius: var(--radius-md);
}
Design System Card
This card uses tokens from our custom property design system.