How to define and use custom properties (CSS variables)
The difference between :root and scoped variables
How to use fallback values for undefined variables
Creating dynamic themes by overriding variables in scope
Combining custom properties with calc() for powerful systems
Using @property to enable custom property animations
Integrating JavaScript with CSS variables for interactivity
Best practices for naming and organizing custom properties
Introduction to Custom Properties
Custom properties (CSS variables) are one of the most powerful features in modern CSS.
Unlike preprocessor variables (Sass, Less), CSS custom properties are live values that
cascade, inherit, and can be modified at runtime—making them perfect for theming,
dynamic styling, and JavaScript integration.
They're defined using --property-name syntax and accessed with the
var() function. Because they're native CSS, they update immediately
when changed, enabling responsive, interactive designs without recompilation.
Basic Usage
Custom properties are typically defined in the :root selector for global
scope, making them available throughout your entire stylesheet.
The var() function accepts a second parameter: a fallback value used when
the custom property isn't defined. This provides safe defaults and prevents broken styles.
.element {
/* If --undefined-color doesn't exist, use #333 */
color: var(--undefined-color, #333);
/* Fallback can be any value, even complex ones */
background: var(--bg-gradient, linear-gradient(135deg, #667eea, #764ba2));
/* Can nest var() with multiple fallbacks */
border-color: var(--border-1, var(--border-2, var(--border-default)));
}
Scoped Variables and Cascade
Custom properties follow the cascade and can be redefined in any selector. This enables
powerful component theming where you override variables within a specific scope.
.card {
--card-bg: white;
--card-text: #333;
--card-border: #e0e0e0;
background: var(--card-bg);
color: var(--card-text);
border: 2px solid var(--card-border);
}
.card.dark {
/* Override only for this card */
--card-bg: #1a1a1a;
--card-text: #e0e0e0;
--card-border: #444;
}
.card.accent {
/* Different overrides for accent cards */
--card-bg: #fff3e0;
--card-text: #e65100;
--card-border: #ffb74d;
}
Theming with Custom Properties
One of the most practical uses of custom properties is building theme systems. By
changing a few variables at a high level, you can transform an entire design.
Combining custom properties with calc() creates powerful design systems
where values are derived from base units. Change the base, and everything scales
proportionally.
:root {
--base-unit: 8;
/* Now derive everything from this base */
}
.element {
margin: calc(var(--base-unit) * 2px); /* 16px */
padding: calc(var(--base-unit) * 1px); /* 8px */
gap: calc(var(--base-unit) * 3px); /* 24px */
}
/* Responsive sizing with variables */
.box {
--size: 100;
width: calc(var(--size) * 1px);
height: calc(var(--size) * 1px);
}
.box:hover {
--size: 150; /* Just change the variable! */
}
Dynamic Gradients
Custom properties work with any CSS value, including complex ones like gradients.
This enables dynamic, customizable backgrounds that can be adjusted with just a
few variable changes.
The @property rule is a modern feature that lets you define the syntax,
initial value, and inheritance of custom properties. This enables smooth animations
of custom properties, which wasn't previously possible.
@property --animated-hue {
syntax: '';
initial-value: 0;
inherits: false;
}
.animated-box {
background: hsl(var(--animated-hue), 70%, 50%);
animation: hue-rotate 10s linear infinite;
}
@keyframes hue-rotate {
to {
--animated-hue: 360;
}
}
/* Also works with angles */
@property --gradient-angle {
syntax: '';
initial-value: 0deg;
inherits: false;
}
.rotating-gradient {
background: linear-gradient(var(--gradient-angle), #667eea, #764ba2);
animation: rotate 5s linear infinite;
}
@keyframes rotate {
to { --gradient-angle: 360deg; }
}
JavaScript Integration
Custom properties can be read and modified via JavaScript, creating a powerful bridge
between your styles and interactivity. This is perfect for user controls, dynamic
theming, and responsive visualizations.
.box {
--box-size: 100;
--box-hue: 200;
width: calc(var(--box-size) * 1px);
height: calc(var(--box-size) * 1px);
background: hsl(var(--box-hue), 70%, 50%);
transition: all 0.3s ease;
}
// Set a custom property
element.style.setProperty('--box-size', '200');
element.style.setProperty('--box-hue', '280');
// Read a custom property
const size = getComputedStyle(element).getPropertyValue('--box-size');
// Set at document level for global changes
document.documentElement.style.setProperty('--primary-color', '#ff0000');
Best Practices
Follow these guidelines to create maintainable, scalable custom property systems:
1. Use Semantic Names
Name variables based on their purpose, not their value. Use --primary-color
instead of --blue, --spacing-large instead of
--space-32.
2. Organize by Category
Group related variables together: colors, spacing, typography, shadows, etc. This makes
them easier to find and maintain.
When you need to animate custom properties, use @property to define
their syntax. This enables smooth transitions instead of instant changes.
6. Document Your Variables
Add comments explaining the purpose and usage of custom properties, especially for
complex design systems:
:root {
/* Primary brand color - used for CTAs, links, highlights */
--primary-color: #2196f3;
/* Base spacing unit (8px) - all spacing derives from this */
--spacing-unit: 0.5rem;
/* Z-index scale - use these instead of arbitrary values */
--z-dropdown: 1000;
--z-modal: 2000;
--z-tooltip: 3000;
}