Short answer: CSS custom properties, often called CSS variables, are entities defined by authors that contain specific values to be reused throughout a document. You declare them with a double hyphen prefix like –main-color: #333; and access them using the var() function, e.g., color: var(–main-color);. They support inheritance, scoping, and dynamic updates via JavaScript, making them more powerful than preprocessor variables.
Key takeaways
- CSS custom properties are natively supported in all modern browsers.
- Use the — prefix to declare, var() to consume.
- Scope is determined by the element cascade and inheritance.
- They can be changed dynamically with JavaScript.
- Great for theming without multiple stylesheet overrides.
- Unlike preprocessor variables, they cascade and inherit.
What you will find here
Have you ever found yourself repeating the same color, font size, or spacing value across dozens of CSS rules? It gets tedious fast. One small design change can force you to search and replace across multiple files. CSS custom properties, commonly called CSS variables, solve this problem elegantly. They let you store values in one place and reference them anywhere in your stylesheet. With native browser support now universal, there’s no reason not to use them.

What Are CSS Custom Properties?
CSS custom properties are entities defined by CSS authors that hold specific values which can be reused throughout a document. They follow the same cascade and inheritance rules as any other CSS property. You define them using a double hyphen prefix like --primary-color, and you retrieve their values using the var() function.
For example, you might set a brand color in a :root rule and then apply it wherever needed:
:root {
--primary-color: #3498db;
}
.button {
background-color: var(--primary-color);
}
Now, if you ever want to change your primary color, you update it in one place. All references update automatically. No find-and-replace. No missed instances.
Declaring and Using CSS Custom Properties
Declaring a custom property is straightforward. You write a property name starting with two hyphens, then a colon, then any value that would be valid for a CSS property. The value can be a color, length, number, string, or even a complex expression.
:root {
--main-bg: #f5f5f5;
--gutter: 16px;
--font-stack: 'Helvetica', Arial, sans-serif;
}
To use these variables, you wrap the property name inside var():
body {
background-color: var(--main-bg);
padding: var(--gutter);
font-family: var(--font-stack);
}
The var() function also accepts a fallback value as a second argument. If the custom property isn’t defined, the fallback gets used:
.card {
border: 1px solid var(--border-color, #ddd);
}
This is especially useful when working with components from third-party libraries or when you want to provide sensible defaults.
Scope and Inheritance
One powerful feature of CSS custom properties is that they follow the cascade. Where you declare a custom property determines its scope. A property declared on :root is available globally. If you declare it on a specific element, it only applies to that element and its descendants.
.alert {
--alert-bg: #ffeeba;
background-color: var(--alert-bg);
}
.alert .close-btn {
color: var(--alert-bg); /* inherits from .alert */
}
This scoping behavior lets you create component-specific overrides without affecting other parts of the page. For example, you might define a default button style with global variables, then override those variables inside a special section to create a variant.
:root {
--btn-bg: #007bff;
--btn-color: #fff;
}
.danger-zone {
--btn-bg: #dc3545;
--btn-color: #fff;
}
Now, any button inside .danger-zone will automatically use the red background, while buttons elsewhere keep the blue. No extra classes, no specificity battles.
Dynamic Updates with JavaScript
Unlike preprocessor variables (like those in Sass or Less), CSS custom properties can be changed at runtime via JavaScript. This opens up many possibilities for interactive themes, user preferences, and animations.
To read a custom property value from an element:
const root = document.documentElement;
const primaryColor = getComputedStyle(root).getPropertyValue('--primary-color').trim();
To set a custom property value:
root.style.setProperty('--primary-color', '#e74c3c');
With this technique, you can build a dark mode toggle that flips a set of variables:
- Define your light theme colors on
:root. - Define alternative dark theme values under a
.dark-modeclass. - When the user clicks a toggle, add or remove the class on the body.
- The cascade updates all colors automatically.
No need to write separate stylesheets or reload the page. The same principle applies to creating interactive dashboards, live previews, or adjusting spacing based on viewport size using JavaScript.

CSS Variables vs. Preprocessor Variables
If you’ve used Sass or Less, you might wonder how CSS custom properties compare. Both help you avoid repetition, but they work differently. Let’s break it down.
| Feature | CSS Custom Properties | Preprocessor Variables (Sass) |
|---|---|---|
| Runtime access | Yes, with JavaScript | No, compiled away |
| Inheritance | Follows CSS cascade | Not inherited; lexical scope |
| Dynamic theming | Easy via class or JS | Requires recompilation |
| Media queries | Can be used inside | Can’t be used inside without workarounds |
| Browser support | Modern browsers | Requires a preprocessor |
| Computation | No built-in math | Can perform arithmetic |
The key takeaway is that preprocessor variables are great for build-time consistency, while CSS custom properties shine for runtime flexibility. For most projects, using both together makes sense: preprocessor variables for static values (like breakpoints or math calculations) and custom properties for dynamic theming.
Practical Use Cases
Consistent Spacing and Sizing
Define a spacing scale as custom properties:
:root {
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
}
Then use them throughout:
.card {
padding: var(--space-md);
margin-bottom: var(--space-lg);
}
.card-title {
margin-bottom: var(--space-sm);
}
This ensures every component adheres to your spacing system. Adjusting the whole design’s rhythm becomes a one-line change.
Theming
As mentioned earlier, custom properties are perfect for theming. You can define a set of color variables and swap them by toggling a class on a parent element:
.dark-theme {
--bg: #222;
--text: #eee;
--link: #80bfff;
}
Apply the class to the body, and all child elements that use these variables update instantly.
Component Variants
Instead of using modifier classes that override specific properties, you can define variant variables:
.btn--primary {
--btn-bg: #007bff;
--btn-color: #fff;
}
.btn--secondary {
--btn-bg: #6c757d;
--btn-color: #fff;
}
.btn {
background-color: var(--btn-bg);
color: var(--btn-color);
padding: 0.5rem 1rem;
border: none;
}
This reduces specificity issues because you’re only varying the variables, not overriding multiple rules.
Responsive Design
You can change custom property values inside media queries:
:root {
--gutter: 8px;
}
@media (min-width: 768px) {
:root {
--gutter: 16px;
}
}
Now any element using --gutter will have more spacing on larger screens. This is cleaner than rewriting margins and paddings for each breakpoint.
Working with calc() and Custom Properties
Custom properties work well with the calc() function, enabling dynamic calculations. For example, you can combine a variable with a fixed unit:
:root {
--base-size: 1rem;
}
.title {
font-size: calc(var(--base-size) * 2);
}
You can also use multiple variables together:
:root {
--column-count: 3;
--gap: 1rem;
}
.grid {
display: grid;
grid-template-columns: repeat(var(--column-count), 1fr);
gap: var(--gap);
}
This pattern makes it easy to adjust layouts by changing just the variable values. Note that you cannot use custom properties inside calc() without the var() wrapper, and the expression must be valid at runtime.
Debugging Custom Properties
When something goes wrong with custom properties, the browser’s developer tools are your best friend. In the Elements panel, you can inspect any element and see the computed value of --custom-property. If the value is blank or shows an error, you might have a typo or a scope issue. Another trick is to temporarily add a fallback to see if the property resolves correctly. For instance, changing var(--my-var) to var(--my-var, red) will paint the element red if the variable is missing, confirming the issue. Common problems include using the wrong case or referencing a variable that was never declared. Always double-check that you are using the exact same name, including hyphens and case.
Common Pitfalls and Tips
- Case sensitivity:
--primaryand--Primaryare different. Stick to a consistent naming convention, like kebab-case. - Missing fallback leads to invalid values: If you use a custom property that isn’t defined and provide no fallback, the property becomes invalid and gets ignored. Always provide a fallback when there’s a chance the variable might not be set.
- Cannot be used in media queries directly: Custom properties don’t work inside
@mediaquery conditions because they are resolved at runtime, not parse time. Use media query ranges or preprocessor variables for breakpoint values. - Inheritance surprises: Because custom properties inherit, a child element might pick up a parent’s variable unintentionally. Override it explicitly if needed.
- Performance considerations: Browser engines handle custom properties efficiently. However, excessive use of
var()inside animations or on many elements can cause slight layout recalculations. Measure if you notice jank.
CSS custom properties are a mature, practical feature that can simplify your stylesheets and make them far more maintainable. Start small: extract your colors, fonts, and spacing into variables. Once you get the hang of it, explore dynamic theming and component variants. Your future self will thank you.
Frequently asked questions
What is the difference between CSS custom properties and variables in Sass?
CSS custom properties are native to the browser and can be changed at runtime using JavaScript. They follow the cascade and inheritance. Sass variables are compiled away and cannot be accessed or changed after the stylesheet is built. Use CSS custom properties for dynamic values and Sass variables for static build-time values.
How do I set a fallback value for a CSS custom property?
Pass a second argument to the var() function: var(–custom-prop, fallback-value). If –custom-prop is not defined, the fallback-value is used. Ensure the fallback is syntactically valid for the property, or the entire declaration becomes invalid.
Can CSS custom properties be used inside media queries?
You cannot use a custom property as the condition in a @media query, because custom properties are resolved dynamically. However, you can change the value of a custom property inside a media query and then use that variable elsewhere in your styles.
Do CSS custom properties have performance issues?
Modern browsers handle custom properties very efficiently. In most cases, the performance impact is negligible. However, if you use var() in many properties that trigger layout or painting, and change those variables frequently (e.g., via JavaScript animations), it may cause extra work. Usually, the benefits outweigh the costs.
How do I inspect CSS custom properties in browser DevTools?
In Chrome DevTools, open the Styles panel and scroll to the bottom where custom properties are listed. You can also see them in the Computed panel. Firefox has a dedicated Variables view in the Inspector. Click on the var() function to see its definition or set new values.



