CSS Custom Properties (CSS Variables) have fundamentally changed how we write CSS. Combined with Firefox's DevTools, they enable dynamic theming, design system management, and live visual debugging that wasn't possible before. This guide covers the technical depth: inheritance, fallbacks, JS interaction, and debugging in Firefox DevTools.
What Makes CSS Variables Different
CSS variables are:
-
Inherited by default — a variable set on
:rootis available anywhere in the document - Dynamic — changing them via JavaScript updates all dependent values immediately
- Cascadeable — they follow the same cascade rules as other CSS properties
Unlike preprocessor variables (Sass $color, Less @color), CSS custom properties:
- Exist at runtime, not just at compile time
- Can be updated via JavaScript
- Work with the browser's native cascade and inheritance
- Don't require a build step
Basic Syntax
/* Define on :root for global scope */
:root {
--color-primary: #0066cc;
--spacing-base: 1rem;
--font-size-large: 1.5rem;
}
/* Use with var() */
.button {
background-color: var(--color-primary);
padding: var(--spacing-base);
}
Fallback Values
var() accepts a fallback as the second argument:
.element {
/* Uses --custom-color if defined, falls back to red */
color: var(--custom-color, red);
/* Fallback can itself use var() */
font-size: var(--font-size-override, var(--font-size-base, 1rem));
}
The fallback is only used if the variable is not defined — not if it's invalid. An invalid variable value causes the property to use its inherited value, not the fallback.
Scoped Variables
Variables can be scoped to specific elements:
:root {
--button-color: blue;
}
/* Override for elements inside .dark-section */
.dark-section {
--button-color: lightblue;
}
button {
background: var(--button-color); /* blue normally, lightblue inside .dark-section */
}
This is how CSS-variable-based theming works: redefine variables on the root for theme switching.
Dark/Light Mode with CSS Variables
:root {
--bg: #ffffff;
--text: #000000;
--accent: #0066cc;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a1a;
--text: #f0f0f0;
--accent: #4da3ff;
}
}
body {
background: var(--bg);
color: var(--text);
}
a {
color: var(--accent);
}
All elements using these variables automatically update when the system dark mode toggles.
JavaScript Interaction
// Read a CSS variable's value
const style = getComputedStyle(document.documentElement);
const primary = style.getPropertyValue('--color-primary').trim();
// Returns '#0066cc' (with possible leading spaces)
// Set a CSS variable via JavaScript
document.documentElement.style.setProperty('--color-primary', '#ff0000');
// All elements using --color-primary update immediately
// Remove a CSS variable (reverts to :root definition)
document.documentElement.style.removeProperty('--color-primary');
// Set on a specific element
const element = document.getElementById('hero');
element.style.setProperty('--highlight', 'yellow');
This pattern enables JavaScript-driven themes without updating class names.
Computed vs. Inherited Values
// getPropertyValue returns the defined value at the current scope
const rootStyle = getComputedStyle(document.documentElement);
rootStyle.getPropertyValue('--spacing') // returns '1rem' (raw string)
// But the computed value of a property using the variable:
const button = document.querySelector('.button');
getComputedStyle(button).getPropertyValue('padding')
// returns actual computed padding, e.g., '16px' (not 'var(--spacing)')
Animation with CSS Variables
CSS variables can be animated, but with a caveat — you need @property to declare the type:
@property --gradient-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@keyframes rotate-gradient {
to {
--gradient-angle: 360deg;
}
}
.animated-gradient {
background: conic-gradient(from var(--gradient-angle), red, blue, red);
animation: rotate-gradient 3s linear infinite;
}
Without @property, the browser treats variables as strings and can't interpolate them for smooth animation.
@property is supported in Firefox 128+ (experimental in earlier versions).
Firefox DevTools: Editing CSS Variables Live
In Firefox's Inspector → Rules panel:
- Variables defined with
--prefix appear in the rules list - Click the value to edit inline — all elements using that variable update in real time
- The color swatch appears next to color variable values — click for a color picker
Computed panel: variables don't appear in the Computed panel (they're not computed properties), but properties using them show their resolved values.
Console debugging:
// In Firefox DevTools console
getComputedStyle(document.documentElement).getPropertyValue('--color-primary')
CSS Variables and Design Systems
Large codebases often use CSS variables as a design token layer:
/* Primitive tokens */
:root {
--blue-400: #60a5fa;
--blue-600: #2563eb;
--gray-100: #f3f4f6;
--gray-900: #111827;
}
/* Semantic tokens (context-meaningful names) */
:root {
--color-action: var(--blue-600);
--color-action-light: var(--blue-400);
--color-background: var(--gray-100);
--color-text: var(--gray-900);
}
/* Components use semantic tokens */
.button-primary {
background: var(--color-action);
color: var(--color-background);
}
Components never reference primitive tokens directly — only semantic ones. Changing the theme means redefining semantic tokens, not touching component CSS.
Performance Considerations
CSS variables have minimal performance overhead in modern browsers. They're computed at paint time — changing a variable triggers a repaint of affected elements, not a full layout recalculation (unless the variable affects layout properties like width or margin).
Avoid changing many variables simultaneously via JavaScript in a tight loop. Batch changes using requestAnimationFrame:
requestAnimationFrame(() => {
document.documentElement.style.setProperty('--header-height', newHeight + 'px');
document.documentElement.style.setProperty('--content-top', (newHeight + 16) + 'px');
});
Top comments (0)