CSSUsing var() & Fallbacks

Using var() & Fallbacks

The var() function reads a custom property value. Beyond the basic var(--name), it supports fallback values, can be nested, works inside other functions like calc(), and has some important edge cases around invalid values and empty custom properties. This page covers the full behaviour of var().

Basic usage

CSS
/* Read a custom property */
color: var(--color-primary);

/* Inside other functions */
width: calc(var(--sidebar-width) + 32px);
background: hsl(var(--hue) var(--saturation) var(--lightness));

/* On any property */
font-size: var(--font-size-lg);
padding: var(--spacing-md);
transform: translateX(var(--offset-x));
transition: color var(--transition-speed) ease;
Fallback values

var() accepts a second argument: a fallback value used when the custom property is not defined:

CSS
/* var(--name, fallback) */
color: var(--color-primary, #0066cc);

/* Fallback can be any valid value for the property */
padding: var(--card-padding, 1rem 1.5rem);
font-family: var(--font-body, system-ui, sans-serif); /* multi-word fallback */

/* Fallback can itself be another var() */
color: var(--color-accent, var(--color-primary, dodgerblue));

/* Fallback used in design systems — allows consumer override with a default */
.badge {
  background: var(--badge-bg, var(--color-primary));
  color: var(--badge-text, white);
}
/* If --badge-bg isn't set, falls back to --color-primary
   If --color-primary also isn't set, would be invalid (no final fallback) */
The fallback in var() only activates when the custom property is not defined — not when it's defined but invalid for the property
This is a subtle but important distinction. If you write `--size: blue` and then use `width: var(--size, 100px)`, the `100px` fallback does NOT activate — because `--size` IS defined (it has the value "blue"). The fact that "blue" is invalid as a `width` value triggers the guaranteed-invalid value behaviour instead (the property is treated as if it wasn't set at all), not the fallback.
Invalid at computed value time

Custom properties store any value — the browser doesn't validate them until they're used. If the stored value is invalid for the property it's used in, the browser doesn't apply the fallback from var() — it uses the property's initial or inherited value instead:

CSS
:root {
  --not-a-color: hello;
}

p {
  color: var(--not-a-color, red);
  /* Despite the fallback 'red', this produces the INITIAL value of color,
     which is the browser's default text color — not red! */
  /* Because --not-a-color IS defined — the fallback only triggers on undefined */
}

/* Contrast with an undefined property: */
p {
  color: var(--undefined-property, red);
  /* --undefined-property is not defined → fallback 'red' IS used */
}
var() fallbacks only activate when the property is not defined — not when it's defined with an invalid value. An invalid value produces the initial or inherited value for the property.
This catches many developers. If a custom property exists but contains a bad value (wrong type, empty string, etc.), the `var()` fallback is skipped and the property uses its initial value. To guard against this, use the `@property` rule (covered in scope and theming) to declare a type and initial value for your custom properties, so the browser can validate the value before use.
Empty and whitespace-only values

CSS
/* An empty custom property IS defined but has no value */
:root {
  --toggle: ;  /* space only — this IS defined */
}

/* Useful for conditional styles */
.card {
  /* When --highlighted is empty (set), the rule is active */
  --highlighted: ;
  box-shadow: var(--highlighted) 0 0 0 3px gold;
  /* → box-shadow: 0 0 0 3px gold — the empty value disappears */
}

/* Toggle: the trick uses an empty property to switch between two values */
:root {
  --is-dark: ; /* empty = falsy in the toggle pattern */
}

.dark {
  --is-dark: initial; /* defined with 'initial' = truthy */
}

/* The Lea Verou toggle trick:
   --on is 'initial' when true, empty when false, and vice versa */
:root { --light: initial; --dark: ; }
.dark { --light: ;        --dark: initial; }

.theme-aware {
  background: var(--light, white) var(--dark, black);
  /* In light: background: white (--dark fallback of black is replaced by empty) */
  /* In dark:  background: black (--light fallback of white is replaced by empty) */
}
Using var() in JavaScript

JS
// Read a custom property
const root = document.documentElement;
const color = getComputedStyle(root).getPropertyValue('--color-primary').trim();
// Returns: " #0066cc" — note possible leading space, trim it

// Set a custom property
root.style.setProperty('--color-primary', '#ff6600');

// Set on a specific element
const card = document.querySelector('.card');
card.style.setProperty('--card-padding', '2rem');

// Remove a custom property (reverts to cascade value)
card.style.removeProperty('--card-padding');

// Respond to user events — live theme switching
document.querySelector('#theme-toggle').addEventListener('click', () => {
  const current = root.style.getPropertyValue('--bg') || '#ffffff';
  root.style.setProperty('--bg', current === '#ffffff' ? '#1a1a2e' : '#ffffff');
});
getPropertyValue returns the raw string value with possible leading whitespace — always .trim() the result before using it in JavaScript
Custom property values in CSS can have leading whitespace (e.g. `--color: #fff` stores ` #fff` with a space). When you read them with `getComputedStyle().getPropertyValue()`, that whitespace is included. Always `.trim()` the result. This is particularly important when comparing strings or parsing numeric values.
Next
How to scope custom properties to components and use them for theming: [Scope, Inheritance & Theming](/css/custom-property-scope).