developmentcssdesign

CSS Custom Properties: How to Use Variables for Consistent, Maintainable Design

CSS custom properties (variables) eliminate magic values and make theming, dark mode, and responsive design dramatically simpler. Here's how to use them properly and where they differ from preprocessor variables.

·6 min read

What Are CSS Custom Properties?

CSS custom properties — officially called custom properties, colloquially called CSS variables — are values you define once and reuse throughout your stylesheet. They're declared with a double dash prefix (--) and accessed with the var() function.

``` :root { --color-primary: #0ea5e9; --spacing-base: 16px; --border-radius: 8px; }

button { background: var(--color-primary); padding: var(--spacing-base); border-radius: var(--border-radius); } ```

How They Differ from Sass/Less Variables

Preprocessor variables (Sass $var, Less @var) are compiled away before the CSS reaches the browser. They don't exist at runtime.

  • **Change dynamically** with JavaScript: element.style.setProperty('--color', '#f00')
  • **Inherit** down the DOM tree like normal CSS properties
  • **Be scoped** to a specific element rather than being global
  • **Respond to media queries** — you can change a variable's value inside a @media block

These capabilities make CSS custom properties more powerful than preprocessors for dynamic theming.

Dark Mode Without JavaScript

The most practical use of CSS custom properties is dark mode theming:

``` :root { --bg: #ffffff; --text: #0f172a; --surface: #f8fafc; }

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

body { background: var(--bg); color: var(--text); } .card { background: var(--surface); } ```

Every element using these variables flips automatically. No JavaScript, no class toggling, no duplicate CSS rules.

Scoped Variables

Custom properties cascade and inherit. A variable defined on :root is global. A variable defined on a component is scoped to that component's subtree:

``` .alert { --alert-color: #ef4444; border: 1px solid var(--alert-color); color: var(--alert-color); }

.alert--success { --alert-color: #22c55e; } ```

The --alert-color variable changes for .alert--success without needing separate border and color declarations.

Fallback Values

var() accepts a fallback as the second argument:

var(--color-brand, #0ea5e9)

If --color-brand is undefined, #0ea5e9 is used. This is useful when building components that can optionally accept theme values.

Using Custom Properties with calc()

One of the most powerful combinations: responsive spacing without media queries.

``` :root { --fluid-space: clamp(1rem, 4vw, 3rem); }

.container { padding: var(--fluid-space); gap: calc(var(--fluid-space) / 2); } ```

Common Patterns

**Design token system** — define all spacing, typography, and color decisions as variables on :root. Everything else uses only variables, never raw values.

**Component API** — expose variables like --button-bg and --button-radius that consumers can override without touching internal implementation.

**Animation targets** — animate a custom property value with JavaScript and let CSS transitions respond naturally.

**Breakpoint multiplier** — set a --scale variable to 1 on mobile and 1.25 on desktop, then multiply all spacing by it.

Browser Support

CSS custom properties are supported in all modern browsers. The only notable exception is Internet Explorer 11, which has no support at all. If IE11 support is required, you need Sass variables as a fallback or a PostCSS polyfill.

Try These Free Tools

More Articles