CSS Variables: How to Use Them With Examples
Learn how CSS custom properties work, how to define and override reusable design tokens, and when to use fallbacks or @property.
CSS variables are formally called custom properties. Define one with a name beginning with two hyphens, then read its value inside another CSS property with var(). For example:
:root {
--brand-color: rebeccapurple;
--space-unit: 0.5rem;
}
.button {
background-color: var(--brand-color);
padding: calc(var(--space-unit) * 2);
}
Ordinary custom properties participate in the cascade and inherit through descendants. That makes them useful for shared design tokens and local overrides. They are values used in property declarations, not general-purpose programming variables: they cannot parameterize selectors, property names, media-query conditions, or container-query conditions.
1. Declare and use a custom property
A custom property declaration uses a double-hyphen name and a value. The property that consumes it uses var(--name):
:root {
--brand-color: rebeccapurple;
}
.button {
color: white;
background-color: var(--brand-color);
}
Custom property names are case-sensitive: --brand-color and --Brand-color are different names. The value is substituted into a property value when the browser computes styles.
2. Put shared tokens on :root
:root matches the document root element. Declaring tokens there is a common way to make them available to the page’s descendants. It is a convention, not a requirement; you can declare a property on a component when you want its scope to stay local.
:root {
--color-brand: #4c2a85;
--color-text: #202124;
--color-surface: #ffffff;
--space-unit: 0.5rem;
--radius-card: 0.75rem;
}
.button {
color: white;
background: var(--color-brand);
padding: calc(var(--space-unit) * 2) calc(var(--space-unit) * 3);
border-radius: var(--radius-card);
}
.card {
color: var(--color-text);
background: var(--color-surface);
border-radius: var(--radius-card);
padding: calc(var(--space-unit) * 4);
}
Keep token names descriptive and consistent. A small set of tokens for color, spacing, and shape can make repeated values easier to change. Avoid creating a token for every value before there is a real need to reuse or override it.
3. Scope and override values
An ordinary custom property applies to the element where it is declared and is inherited by descendants. A local declaration can override an inherited value through the normal cascade:
:root {
--surface-color: white;
--text-color: #222;
}
.card {
color: var(--text-color);
background-color: var(--surface-color);
}
.card--dark {
--surface-color: #222;
--text-color: white;
}
Place card--dark on a card to change the values used within that element’s subtree. This is not global text replacement: a custom property belongs to an element and its descendants, and unrelated siblings do not read a locally declared value.
When declarations for the same custom property apply to the same element, normal cascade rules decide which value wins, including origin, importance, and specificity. A nearer declaration on a descendant can then override the inherited value for that descendant and its children.
4. Use fallback values with var()
The optional second argument is used when the referenced custom property has the guaranteed-invalid value, such as an ordinary unregistered property that has not been set:
.notice {
color: var(--notice-color, #333);
}
You can nest var() calls to provide a chain of fallbacks:
.panel {
background-color: var(--panel-color, var(--surface-color, white));
}
The fallback is a runtime CSS fallback for an unavailable custom property value. It does not make a browser that lacks custom-property support understand var(); use a separate earlier declaration or an appropriate compatibility strategy if you need to support such a browser.
5. Understand invalid substitutions
A custom property can hold tokens that are not valid for every consuming property. The browser checks whether the substituted value makes sense for the destination property. For example, a length is not a valid color:
:root {
--text-color: 16px;
}
p {
color: var(--text-color);
}
Here the resulting color declaration is invalid at computed-value time. The var() fallback is not selected just because the substituted value is invalid for color; the fallback handles an unavailable or guaranteed-invalid referenced custom property. Keep tokens semantically appropriate for their intended use, or use @property when a constrained syntax is useful.
6. Register a property with @property
The optional @property rule lets you specify a custom property’s syntax, whether it inherits, and its initial value:
@property --progress {
syntax: "<percentage>";
inherits: false;
initial-value: 0%;
}
.progress-bar {
width: var(--progress);
}
Registration can constrain the value type, disable inheritance, and provide a defined initial value. Registered typed values can also be animated. For simple reusable tokens, ordinary double-hyphen properties are usually enough; registration is useful when those extra rules matter.
| Behavior | Ordinary custom property | Registered with @property |
|---|---|---|
| Syntax/type constraint | No declared syntax | Can declare a syntax |
| Inheritance | Inherits | Set with inherits |
| Initial value | Unset values are guaranteed-invalid | Can define an initial value |
| Typed animation | Values are untyped token sequences | Registered typed values can animate |
MDN marks @property Baseline 2024. Check the compatibility data for the browsers and embedded webviews you support before relying on it. MDN describes var() as widely available and available across browsers since April 2017; still verify requirements for a specific browser target. MDN: Using CSS custom properties, MDN: var(), MDN: @property.
7. Know where custom properties work
var() substitutes into property values. It does not turn custom properties into arbitrary CSS variables that can alter the grammar around those values.
- Property values: Supported use, such as
color: var(--text-color)orpadding: var(--space). - Selectors: You cannot use a custom property to construct a selector.
- Property names: You cannot use one to construct a property name.
- Media and container query conditions: Query conditions must be written directly, such as
@media (min-width: 48rem). Custom properties may still be used in declarations inside those rules.
@media (min-width: 48rem) {
.layout {
--column-gap: 2rem;
gap: var(--column-gap);
}
}
8. Build a small token system
This example combines global tokens, a component-level theme override, and a fallback. Save it as an HTML file and open it in a browser:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Custom property example</title>
<style>
:root {
--brand: #4c2a85;
--space: 0.5rem;
--surface: #fff;
--text: #202124;
}
body {
margin: 2rem;
color: var(--text);
font: 1rem/1.5 system-ui, sans-serif;
}
.card {
--card-surface: var(--surface, white);
max-width: 32rem;
padding: calc(var(--space) * 4);
color: var(--text, #222);
background: var(--card-surface);
border: 1px solid #ddd;
border-radius: 0.75rem;
}
.card--dark {
--card-surface: #222;
--text: white;
}
.button {
padding: calc(var(--space) * 2) calc(var(--space) * 3);
color: white;
background: var(--brand, rebeccapurple);
border: 0;
border-radius: 0.5rem;
}
</style>
</head>
<body>
<article class="card card--dark">
<h1>Reusable tokens</h1>
<p>The card overrides inherited values for its subtree.</p>
<button class="button">Continue</button>
</article>
</body>
</html>
To adapt it, first identify values repeated across components. Declare shared values in :root, replace repeated literals with var(), then put overrides on the smallest subtree that needs them. Add fallbacks for genuinely optional values and consider registration only when syntax, inheritance, or typed animation needs to be explicit.
9. Troubleshoot common problems
| Symptom | Likely cause | Fix |
|---|---|---|
The declaration using var() appears unset |
The custom property is not defined on the element or an ancestor, or the name does not match. | Check spelling and capitalization; define it on the element, an ancestor, or in a fallback. |
| A fallback does not appear | The custom property exists, but its substituted value is invalid for the consuming property. | Use a value valid for that destination property. A var() fallback does not repair an invalid substitution. |
| A component does not receive an override | The declaration is outside its ancestor chain, loses in the cascade, or targets a different element. | Place the override on the component or an ancestor; inspect matching declarations, specificity, and importance. |
| A breakpoint expression using a token fails | Custom properties cannot be substituted into media or container query conditions. | Write the condition directly in the query and use tokens in declarations within its rule. |
| The property works in one browser but not an older target | The target may lack support for custom properties or for the newer @property feature. |
Check compatibility for the actual browser versions and webviews; provide a suitable fallback strategy where required. |
| A registered property behaves unexpectedly | Its syntax, initial value, or inheritance setting may not match how the component uses it. | Review the syntax, inherits, and initial-value descriptors, then test the intended value type. |
10. Performance and maintenance
Custom properties are a CSS mechanism, not a network request or a separate runtime library. Their practical maintenance benefit comes from keeping repeated values in one place and making scoped overrides explicit. Avoid treating them as a way to build arbitrary CSS syntax: they only substitute into property values, and a bad value can invalidate the consuming declaration.
For reliability, keep names consistent, choose values that fit their consuming properties, and verify important overrides at the element where they are used. When targeting older browsers, check current compatibility information for the exact feature and browser set instead of assuming that support for var() implies support for @property.
Or skip the browser setup
If you need a screenshot of a page that demonstrates your CSS, ScreenshotNeo can capture it with one API request. The API accepts a URL and returns a PNG, JPEG, WebP, or PDF; see the API documentation for options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = await res.arrayBuffer();
- Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and whether the request was billed.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdffor AI agents and MCP clients. - The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
FAQ
Are CSS variables the same as custom properties?
“CSS variables” is the common name; the formal CSS feature is custom properties declared with names such as --brand-color.
Do custom properties inherit?
Ordinary double-hyphen custom properties inherit. A registered property can opt out with inherits: false.
Can I use a CSS variable for a breakpoint?
No. Write the media or container query condition directly. You can use custom properties in property declarations inside the query’s rules.
Does a fallback guarantee that an invalid value will be replaced?
No. A var() fallback handles an unavailable or guaranteed-invalid custom property, not a value that becomes invalid for the consuming property after substitution.


