CSS Custom Properties (Variables)

Define once, use everywhere — and change them at runtime

Declaring and Using Variables

Declare custom properties with the -- prefix. Use them with the var() function. Typically you declare global variables on :root:

:root { --primary: #6366f1; --radius: 8px; --spacing: 16px; } .button { background: var(--primary); border-radius: var(--radius); padding: var(--spacing); }
Styled with variables

Fallback Values

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

.card { /* If --card-bg is not set, use #f8fafc */ background: var(--card-bg, #f8fafc); /* Chained fallback */ color: var(--card-text, var(--text, #1e293b)); }

The fallback only triggers when the variable is completely undefined. If the variable is set to an invalid value for the property, the fallback is not used — the property falls back to its inherited or initial value instead.

Scoping

Variables cascade and inherit just like regular CSS properties. You can override them on any element:

:root { --accent: #6366f1; /* default purple */ } .section-danger { --accent: #ef4444; /* override to red in this section */ } .button { background: var(--accent); /* purple by default, red inside .section-danger */ }
Default
Danger Scope
Success Scope

Dark Mode Theming

CSS variables make light/dark mode trivial. Define your color tokens once, swap them in a media query or class toggle:

:root { --bg: #ffffff; --text: #1e293b; --surface: #f8fafc; --border: #e2e8f0; } @media (prefers-color-scheme: dark) { :root { --bg: #0f172a; --text: #e2e8f0; --surface: #1e293b; --border: #334155; } } /* Or toggle with a class */ .dark { --bg: #0f172a; --text: #e2e8f0; }

Every element using these variables updates automatically. No duplicate CSS needed.

Changing Variables with JavaScript

Unlike preprocessor variables, CSS custom properties are live. You can read and write them from JS:

// Set a variable on :root document.documentElement.style.setProperty('--primary', '#ef4444'); // Set on a specific element element.style.setProperty('--card-width', '300px'); // Read a variable's computed value const value = getComputedStyle(document.documentElement) .getPropertyValue('--primary');

This is powerful for user-customizable themes, sliders that control spacing or colors, and animations driven by scroll position.

Practical Patterns

Component API

/* The component exposes these variables for customization */ .card { --card-padding: 16px; --card-radius: 8px; --card-bg: var(--surface); padding: var(--card-padding); border-radius: var(--card-radius); background: var(--card-bg); } /* Consumers override without touching the component CSS */ .compact .card { --card-padding: 8px; --card-radius: 4px; }

Responsive Variables

:root { --container-padding: 16px; --heading-size: 1.5rem; } @media (min-width: 768px) { :root { --container-padding: 32px; --heading-size: 2rem; } } @media (min-width: 1280px) { :root { --container-padding: 48px; --heading-size: 2.5rem; } }

Frequently Asked Questions

What is the difference between CSS variables and Sass variables?
CSS variables are live in the browser — they cascade, inherit, and can be changed with JavaScript at runtime. Sass variables compile to static values at build time. Use CSS variables for theming and dynamic values. Use Sass variables for build-time constants.
Can CSS variables be used in media queries?
Not in the condition (@media (min-width: var(--bp)) does not work). But you can change variable values inside media queries: @media (min-width: 768px) { :root { --spacing: 32px; } }. All elements using that variable update automatically.
How do I set a fallback value for a CSS variable?
Use the second argument: var(--primary, #6366f1). If --primary is not defined, it uses #6366f1. You can chain: var(--a, var(--b, red)).

Related Tools