CSS Custom Properties (Variables) Guide — var() Explained with Examples

CSS custom properties — commonly called CSS variables — let you define reusable values in one place and reference them throughout your stylesheet using the var() function. They're natively supported in all modern browsers and eliminate the need for preprocessor variables in many cases.

What Are CSS Custom Properties?

CSS custom properties are entities defined by CSS authors that contain specific values to be reused throughout a document. They follow a special syntax: property names that begin with two dashes (--). You define them once, typically on the :root selector for global scope, then reference them anywhere using the var() function.

:root {
  --primary-color: #10b981;
  --font-size-base: 16px;
  --spacing-unit: 8px;
}

.button {
  background-color: var(--primary-color);
  font-size: var(--font-size-base);
  padding: calc(var(--spacing-unit) * 1.5) calc(var(--spacing-unit) * 3);
}

Unlike preprocessor variables (Sass $variable, Less @variable), CSS custom properties are resolved at runtime by the browser, not at compile time. This means they can be changed dynamically with JavaScript, updated via media queries, and scoped to specific elements.

Defining CSS Variables

Custom properties are defined using standard CSS property syntax with the -- prefix:

selector {
  --variable-name: value;
}

The value can be any valid CSS value: a color, length, number, string, or even a complex value like a gradient or multi-value shorthand. Custom property names are case-sensitive: --Color and --color are different properties.

Global vs. Scoped Variables

Defining custom properties on :root makes them globally available throughout the document:

:root {
  --brand-color: #3b82f6;
  --border-radius: 6px;
}

Defining them on a specific selector scopes them to that element and its descendants:

.card {
  --card-padding: 1.5rem;
  --card-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.card-body {
  padding: var(--card-padding); /* works — inside .card */
}

.header {
  padding: var(--card-padding); /* undefined — outside .card */
}

Using the var() Function

Reference a custom property anywhere a standard CSS value is accepted:

element {
  property: var(--custom-property-name);
  property: var(--custom-property-name, fallback-value);
}

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

.button {
  background: var(--button-bg, #6366f1); /* uses #6366f1 if --button-bg is undefined */
  color: var(--button-color, white);
}

Fallbacks can themselves be var() calls:

color: var(--text-primary, var(--text-default, #111827));

Key Features of CSS Custom Properties

  • Live in the cascade: Custom properties participate in the CSS cascade and can be overridden via specificity, inheritance, and !important.
  • Inherited by default: Like most CSS properties, custom properties are inherited. A variable defined on a parent element is accessible to all its descendants.
  • JavaScript-accessible: Read and write custom properties at runtime with getComputedStyle(el).getPropertyValue('--var') and el.style.setProperty('--var', value).
  • Responsive via media queries: Redefine custom property values inside media queries to change multiple related properties at once.
  • Work with calc(): Combine with calc() for derived values: calc(var(--spacing) * 2).
  • No preprocessor required: Works natively in all modern browsers — no build step needed.

Use Cases

Design Tokens and Theming

CSS variables are ideal for design tokens — the named values that make up a design system. Define all brand colors, typography sizes, spacing values, and border radii as custom properties on :root. Every component references these tokens, so changing a token updates the entire UI:

:root {
  --color-brand: #10b981;
  --color-brand-dark: #059669;
  --color-text: #1f2937;
  --color-surface: #ffffff;
  --radius-md: 8px;
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.12);
}

Dark Mode Implementation

CSS variables make dark mode straightforward. Define light-mode values on :root, then override them in a dark-mode media query or a [data-theme="dark"] attribute:

:root {
  --bg: #ffffff;
  --text: #111827;
  --surface: #f9fafb;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f172a;
    --text: #f1f5f9;
    --surface: #1e293b;
  }
}

/* Or toggle with JS: */
[data-theme="dark"] {
  --bg: #0f172a;
  --text: #f1f5f9;
  --surface: #1e293b;
}

Responsive Typography and Spacing

Redefine spacing and font-size variables at different breakpoints instead of writing repetitive media query overrides:

:root {
  --font-size-hero: 2.5rem;
  --section-padding: 5rem;
}

@media (max-width: 768px) {
  :root {
    --font-size-hero: 1.75rem;
    --section-padding: 2.5rem;
  }
}

.hero h1 { font-size: var(--font-size-hero); }
.section { padding: var(--section-padding) 0; }

Component-Level Customization

Expose custom properties as a component API, allowing consumers to customize a component without overriding internal styles:

/* Component default */
.badge {
  --badge-bg: #e5e7eb;
  --badge-color: #374151;
  background: var(--badge-bg);
  color: var(--badge-color);
}

/* Consumer customization */
.badge-success {
  --badge-bg: #d1fae5;
  --badge-color: #065f46;
}

JavaScript-Driven Animations

Update custom properties from JavaScript to drive CSS transitions and animations without inline styles on every animated property:

// JavaScript
document.documentElement.style.setProperty('--mouse-x', `${event.clientX}px`);
document.documentElement.style.setProperty('--mouse-y', `${event.clientY}px`);

/* CSS */
.spotlight {
  background: radial-gradient(
    circle at var(--mouse-x) var(--mouse-y),
    rgba(255,255,255,0.15),
    transparent 40%
  );
}
→ Try CSS Tools Free at DevKits
aiforeverthing.com — CSS generators and developer tools, no signup

CSS Variables vs. Sass/Less Variables

Both solve the "define once, use everywhere" problem, but they work differently:

  • Compile-time vs. runtime: Sass variables are replaced at build time — the output CSS contains no variable references. CSS custom properties remain in the output and are resolved live by the browser.
  • Dynamic updates: CSS variables can be changed with JavaScript or in response to user interaction; Sass variables cannot.
  • Media query responsiveness: CSS variables can be redefined inside @media blocks; Sass variables cannot (they're static at compile time).
  • Inheritance and cascade: CSS variables follow the CSS cascade; Sass variables are scoped to the file/function.
  • Build tooling: Sass requires a preprocessor; CSS variables work natively in browsers.

Many projects use both: Sass for build-time constants (like breakpoint values used in @media conditions) and CSS variables for runtime-changeable values like theme colors.

Browser Support and Fallbacks

CSS custom properties are supported in all modern browsers (Chrome 49+, Firefox 31+, Safari 9.1+, Edge 16+). Internet Explorer 11 does not support them. For IE11 fallbacks, provide a static value before the var() call:

.button {
  background: #10b981; /* IE11 fallback */
  background: var(--primary-color); /* modern browsers */
}

You can also use @supports to detect support:

@supports (--css: variables) {
  /* styles for browsers that support custom properties */
}

Frequently Asked Questions

Can CSS custom properties hold any value?

Yes — they can hold any valid CSS value: colors, lengths, numbers, strings, percentages, gradients, shadow values, or even partial values used in calc(). They can also hold empty values or whitespace. They cannot hold at-rules or selector syntax.

Why isn't my CSS variable working?

Common causes: the variable is defined on a selector that isn't an ancestor of where you're using it (scope issue); the variable name has a typo (case-sensitive); the value contains a syntax error; or you're testing in a browser that doesn't support custom properties. Check the browser devtools — undefined variables show as empty in computed styles.

Can I use CSS variables in media query conditions?

No. CSS variables cannot be used in media query conditions like @media (max-width: var(--breakpoint)). Media query conditions are evaluated before custom properties are resolved. Use Sass or JS constants for breakpoint values.

Are CSS variables the same as CSS preprocessor variables?

No. Preprocessor variables (Sass $var, Less @var) are a build-time feature — they're substituted before the browser sees the CSS. CSS custom properties are a browser-native runtime feature that persists in the final CSS output and can be changed dynamically.

How do I read a CSS variable value in JavaScript?

Use getComputedStyle: getComputedStyle(document.documentElement).getPropertyValue('--variable-name').trim(). To set: document.documentElement.style.setProperty('--variable-name', newValue).

Recommended Hosting for Developers

  • Hostinger — From $2.99/mo. Excellent for static sites and Node.js apps.
  • DigitalOcean — $200 free credit for new accounts. Best for scalable backends.
  • Namecheap — Budget-friendly shared hosting with free domain.