CSS Variables (officially called CSS Custom Properties) let you store values in one place and reuse them throughout your stylesheets. Less repetition, more readable code, and real flexibility.
Unlike preprocessor variables from Sass or Less, CSS Variables are part of the DOM. They cascade, inherit, and can be read or changed with JavaScript at runtime.
Why Use CSS Variables?
CSS is a declarative language, not a programming one. But in any complex site you’ll find values that repeat dozens of times – a brand color, a spacing unit, a font stack.
If you want to change that color, you’d need to Search & Replace across every stylesheet. CSS Variables fix this by letting you store a value once and reference it everywhere.
There’s a semantic benefit too. --main-text-color is far easier to understand than #ffe01b, especially when the same color appears in different contexts.
Basic Usage of CSS Variables
The basic usage of CSS Variables is as follows:
Declaration of a variable:
element {
--main-bg-color: brown;
}Using the variable:
element {
background-color: var(--main-bg-color);
}Here’s a simple example I created on CodePen showing CSS Variables in action:
See the Pen CSS Variables Example by Roee Yossef (@roeey) on CodePen.
Declaration of CSS Variables
First, decide on the scope. If you want the variable available everywhere, define it on the :root pseudo-class (which targets the <html> element).
Since custom properties cascade, a variable on :root is available to every element in the DOM.
:root {
--main-color: #ffe01b;
}As you can see, we declare the variable just like we set any CSS property. However, a variable must start with two dashes.
To access the variable as we saw earlier, you need to use the var() function and pass the variable name as a parameter.
#title {
color: var(--main-color);
}This will apply the color #ffe01b to an element with the ID title.
Inheritance of CSS Variables
These Custom Properties also inherit. This means that if a value is not defined for a specific property of an element, the value of the parent element’s property will be used. Look at the following HTML:
<div class="one">
<div class="two">
<div class="three"></div>
<div class="four"></div>
</div>
</div>Assuming you have the following CSS:
.two {
--test: 10px;
}
.three {
--test: 2em;
}In this case, the result of var(--test) is:
- For the element with
class="two, the value will be 10px. - For the element with
class="three, the value will be 2em. - For the element with
class="four, the value will be 10px (inherited from the parent element). - For the element with
class="one, the value will be invalid. So, the default value for the property will be used.
Keep in mind that these are Custom Properties, not programming-language variables. The value is computed where it’s needed and doesn’t carry over to siblings. A property set on .two won’t be available to an unrelated .five element – only to .two and its children.
For example, say you have a warning component with a unique color that’s only relevant to that component:
.alert {
--alert-color: #ffe01b;
}You can use this variable for the element and for all its children in the DOM:
.alert p {
color: var(--alert-color);
border: 1px solid var(--alert-color);
}If you try to use the variable alert-color for a different selector that is not .alert (or its children), it simply won’t work. In this case, the browser will ignore this CSS rule.
It’s worth mentioning that CSS variables follow the normal CSS behavior, so the last property that the browser receives is the one it will take.
How to Access Variables Using JavaScript
Because CSS Variables live in the DOM, you can read and update them with JavaScript. This opens the door to runtime theming – letting users change colors, font sizes, or spacing without reloading the page.
Reading a variable takes three lines:
const root = document.querySelector(':root');
const rootStyles = getComputedStyle(root);
const mainColor = rootStyles.getPropertyValue('--main-color');
console.log(mainColor);
--> '#ffe01b'Updating is a single call to setProperty:
root.style.setProperty('--main-color', '#a3316f')
Simpler Responsiveness with CSS Variables
CSS Variables give you a clean way to handle responsive design. Override variable values inside media queries, and every element referencing them updates automatically:
:root {
--main-font-size: 16px;
}
@media all and (max-width: 600px) {
:root {
--main-font-size: 12px;
}
}Example: Modular Scale with CSS Variables
A modular scale is a mathematical ratio used to pick consistent typography sizes. Let’s use a scale of 1.2 for small screens and 1.33 for large screens. The resulting sizes from modularscale.com:
| 1.2 | 1.33 |
|---|---|
| 2.488rem | 4.209rem |
| 2.074rem | 3.157rem |
| 1.728rem | 2.369rem |
| 1.44rem | 1.777rem |
| 1.2rem | 1.333rem |
| 1rem | 1rem |
This is a perfect use case for CSS Variables. Define one set of variables and swap their values at different breakpoints:
:root {
/* scale for 1.2 */
--font-size-1: 1rem;
--font-size-2: 1.2rem;
--font-size-3: 1.44rem;
--font-size-4: 1.728rem;
--font-size-5: 2.074rem;
--font-size-6: 2.488rem;
}
@media screen and (min-width: 800px) {
:root {
/* scale for 1.33 */
--font-size-1: 1rem;
--font-size-2: 1.333rem;
--font-size-3: 1.777rem;
--font-size-4: 2.369rem;
--font-size-5: 3.157rem;
--font-size-6: 4.209rem;
}
}
The responsive logic lives in the variables. The rest of the CSS stays clean:
h1 {
font-size: var(--font-size-6);
}
h2 {
font-size: var(--font-size-5);
}
h3 {
font-size: var(--font-size-4);
}
h4 {
font-size: var(--font-size-3);
}
h5 {
font-size: var(--font-size-2);
}
h6 {
font-size: var(--font-size-1);
}
Do CSS Variables Replace the Need for Preprocessors?
Most of what we’ve covered can also be done with SCSS. But CSS Variables have distinct advantages:
- They are native to the browser and do not require compilation as Preprocessors do.
- They are live and exist in the DOM, allowing you to access these variables in JavaScript – a significant advantage.
- They can be changed and can react to what’s happening on the page, unlike the static variables of Preprocessors.
So, can CSS Variables replace the use of Preprocessors? No.
The use of Preprocessors is still relevant, and in fact, it would be a good idea to keep static variables in Sass (or any Preprocessor you’re using) as shown in the example below:
$breakpoint-small: 640px;
$theme-color: aliceblue;
@media screen and (min-width: $breakpoint-small) {
body {
--background: $theme-color;
}
}
This also highlights a key limitation: CSS Variables can only be used in property values, not in selectors or media query expressions. You cannot write @media (min-width: var(--bp)).
If you try to change line 5 in the above code to contain a CSS variable, the code won’t work properly.
Additional Tips
Case sensitivity. CSS Variables are case-sensitive:
:root {
--color: blue;
--COLOR: red;
}
/*--color and --COLOR are two different variables*/Fallback values. The var() function accepts a second argument as a fallback:
width: var(--custom-width, 33%);
Inline usage. You can set CSS Variables directly in HTML:
<!--HTML-->
<html style="--size: 600px">
<!--CSS-->
body {
max-width: var(--size)
}Composing variables. Use one variable inside another:
--base-red-color: #f00;
--background-gradient: linear-gradient(to top, var(--base-red-color), #222);Variables with calc(). Combine variables with calculations:
--text-input-width: 5000px;
max-width: calc(var(--text-input-width) / 2);Registered Custom Properties with @property
Since July 2024, the @property at-rule is supported in all major browsers. It lets you register a custom property with a specific type, an initial value, and inheritance control:
@property --brand-color {
syntax: "<color>";
inherits: true;
initial-value: #3490dc;
}Why does this matter? Regular custom properties are treated as strings by the browser. It can’t interpolate between #3490dc and #e3342f during a transition because it doesn’t know they’re colors.
With @property, the browser understands the type and can animate the property smoothly:
@property --gradient-angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.card {
background: linear-gradient(var(--gradient-angle), #3490dc, #6574cd);
transition: --gradient-angle 0.6s ease;
}
.card:hover {
--gradient-angle: 180deg;
}Without
@property, gradient transitions jump instantly. With it, they animate smoothly – something that was previously impossible in pure CSS.
For a complete guide with live examples and more use cases, see the CSS @property at-rule guide.
Browser Support
CSS Variables have been supported in all modern browsers for years, with over 97% global coverage. Internet Explorer was the only holdout, and with its retirement in 2022, fallbacks for IE are no longer necessary.
The newer @property at-rule reached universal browser support in July 2024 (Chrome 85+, Firefox 128+, Safari 16.4+).
FAQs
@property and specify a syntax like <color> or <angle>, the browser can interpolate values and animate transitions smoothly.--) was chosen to avoid conflicts with existing and future CSS properties. Any property name starting with -- is guaranteed to never collide with a native CSS property.@media (min-width: var(--bp)) will not work. You can, however, change variable values inside a media query block to affect how properties that reference them behave.@property registers a custom property with a specific type (syntax), an initial value, and an inheritance flag. This gives the browser type information it needs to animate the property, validate values, and provide a reliable fallback when invalid values are assigned.Conclusion
I use CSS Variables on every project now. They’ve replaced most of my Sass variables for anything related to colors, spacing, and typography. The combination of cascading scope, JavaScript access, and the newer @property registration makes them far more capable than when they first shipped.
If CSS layout is your next focus, check out my CSS Grid guide or the post on native CSS nesting.

