CSS variables

On this page 7

CSS variables, formally called CSS custom properties, are properties whose names begin with two hyphens, such as --brand-color, and whose values are read elsewhere with the var() function. They let a stylesheet store a value once and reuse it everywhere, and unlike preprocessor variables they are live in the browser: they follow the cascade, inherit through the DOM and can change at runtime.

In CSS and front-end development, custom properties are the foundation of design tokens, theming, dark mode and component configuration. A single change to a variable on the root element or on a component can update every value that depends on it, without rebuilding the stylesheet.

How CSS custom properties work

A custom property is declared like any other property, for example --space-m: 16px; inside a selector, and used with var(--space-m). Declarations on :root make a variable available to the entire document, while declarations on a component selector scope it to that subtree. Names are case-sensitive, so --Color and --color are different variables.

Custom properties take part in the normal cascade and specificity rules and are inherited by default. A child element therefore sees the nearest ancestor’s value unless it declares its own. The browser substitutes the value only when computing the property that uses it, which is why a variable can be redefined inside a media query or by a class and immediately affect every dependent declaration.

The var() function accepts a fallback value as its second argument, as in var(--accent, steelblue). The fallback is used when the variable is not defined. If a variable exists but produces an invalid value for the property, the declaration becomes invalid at computed-value time and falls back to the property’s inherited or initial value, not to an earlier declaration.

ConstructExampleEffect
Declaration–radius: 8pxDefines a custom property on an element
Global scope:root with –brand: #0057b8Makes the value available document-wide
Usageborder-radius: var(–radius)Substitutes the stored value
Fallbackvar(–gap, 1rem)Uses 1rem when –gap is undefined
Calculationcalc(var(–space) * 2)Builds derived values from variables
Runtime updatestyle.setProperty in JavaScriptChanges the value without new CSS
Core syntax of CSS variables

CSS variables compared with preprocessor variables

CSS variables are often compared with Sass variables, but the two operate at different times. Sass variables are resolved during compilation and disappear from the output, while custom properties remain in the delivered CSS and are resolved by the browser for each element.

AspectCSS custom propertiesSass variables
ResolvedIn the browser at runtimeAt build time
ScopeDOM tree, through inheritanceLexical blocks in the source file
Responds to media queriesYes, can be redefined inside themNo, values are already fixed
Changeable by JavaScriptYesNo
Usable in selectors and media query conditionsNoYes
Logic and loopsLimited to calc and similar functionsFull functions, maps and loops
Custom properties and Sass variables

In practice the two are complementary rather than competing. Many teams use Sass or a token build tool to generate a list of custom properties, then let the browser handle theming and responsive changes. Utility frameworks such as Tailwind CSS likewise expose their design tokens as custom properties.

Typed custom properties with the property rule

Ordinary custom properties are untyped token streams: the browser does not know whether --angle holds a degree, a color or arbitrary text. The @property at-rule, and its JavaScript equivalent, registers a custom property with a syntax, an inheritance behavior and an initial value.

Registration has two important benefits. First, the browser can animate and transition the variable, interpolating, for example, a registered angle inside a gradient, which is impossible with an unregistered property. Second, invalid values fall back to the registered initial value, making components more robust.

DescriptorPurposeExample value
syntaxDefines the accepted value typelength, color, angle or percentage types
inheritsControls inheritance to descendantstrue or false
initial-valueDefault when nothing valid is set0deg or transparent
Descriptors of the property rule

Practical patterns for CSS variables

Custom properties are most valuable when they encode design decisions rather than raw values. A common structure uses two layers: primitive tokens such as --blue-600 and semantic tokens such as --color-action that reference them. Components consume only the semantic layer, so a theme switch redefines the semantic values while components stay untouched.

  • Theming and dark mode: redefine semantic color variables under a theme class or a prefers-color-scheme media query.
  • Component APIs: expose variables such as –card-padding so that consumers adjust a component without overriding its internals.
  • Responsive values: change spacing or type scale variables at a breakpoint or inside a container query.
  • JavaScript bridges: write pointer positions or scroll progress into variables and let CSS handle the visual response.
NeedRecommended locationReason
Brand-wide design tokens:rootAvailable to every element
Theme variationsTheme class or color-scheme media queryOverrides tokens in one place
Component settingsComponent’s own selectorKeeps values scoped and predictable
One-off instance changeInline style or modifier classAdjusts a single element safely
Choosing where to define a variable

Pitfalls and limitations

Custom properties are powerful but have behaviors that surprise developers used to preprocessors or static values.

  • No use in media query conditions: a breakpoint cannot be written as a variable inside a media query condition.
  • Silent failures: a typo in a variable name produces no error, only an invalid or fallback value.
  • Invalid at computed-value time: a wrong type does not revert to the previous declaration, which can unexpectedly reset a property.
  • Unregistered variables do not animate smoothly; they switch discretely unless registered with a type.
  • Too many layers of indirection make debugging harder; browser developer tools show computed values, but long chains still obscure intent.

A practical recommendation is to name variables by purpose, document the token layers, and provide fallbacks only where a variable may legitimately be absent.

Stripped to the essentials

CSS variables are named containers for style values such as colors, sizes and spacing. Instead of typing the same blue in two hundred places, a developer stores it once under a name and refers to that name everywhere.

When the stored value changes, every place that uses it updates at once. Because this happens live in the browser, a website can switch to a dark theme or bigger spacing instantly, without loading a different stylesheet.

Where it is applied

A software company maintains a design system used by six web applications. Colors were once hard-coded in hundreds of component styles, so adding a dark theme seemed to require editing every file. The front-end team introduces semantic custom properties such as --color-surface, --color-text and --color-action, defined on :root.

A dark theme then becomes a single block of overrides under a theme class and a prefers-color-scheme media query. The team also registers an angle variable with @property so that a loading indicator’s gradient rotates smoothly.

All six applications gain a consistent dark theme after updating only the token file, and later brand color changes are delivered by editing one value instead of searching the codebase.