// blog/developer/
Back to Blog
Developer · June 22, 2026 · 9 min read · Updated May 22, 2026

CSS Custom Properties: The Practical Guide to Variables

CSS Custom Properties: The Practical Guide to Variables

CSS custom properties changed how front-end developers think about stylesheets. Before they arrived, keeping colors, spacing, and font sizes consistent across a large CSS file was a chore. You either memorized hex codes or relied on a preprocessor like Sass to manage variables for you. Now the browser handles variables natively, and the result is simpler, more flexible CSS you can update from one place.

The syntax is straightforward. Define a variable with a double-dash prefix, and use it with the var() function. That is the entire API. The real power shows up when you combine custom properties with the cascade, media queries, and JavaScript. That is where things get useful, not just convenient.

* * *

Defining and Using CSS Variables

A CSS custom property is declared inside a ruleset, typically on the :root selector so it is available everywhere in your document:

`css :root { --color-primary: #2563eb; --color-text: #1f2937; --spacing-base: 16px; --font-body: 'Inter', sans-serif; } `

You reference these values with var():

`css .button { background-color: var(--color-primary); padding: var(--spacing-base); font-family: var(--font-body); color: white; } `

The var() function accepts a second argument as a fallback value. If the variable is not defined or is invalid, the fallback kicks in:

`css .card { border-radius: var(--card-radius, 8px); } `

This fallback pattern is especially useful when building component libraries. Your components work with sensible defaults, but consumers can override them by setting the variable higher up in the cascade.

One thing that trips people up: custom properties are case-sensitive. --Color-Primary and --color-primary are two different variables. Stick with lowercase and dashes to avoid confusion.

Code editor showing CSS custom properties syntax
Code editor showing CSS custom properties syntax
* * *

Building a Color System with Custom Properties

The most common use case for CSS variables is managing colors. Instead of scattering hex values throughout your stylesheet, you define them once and reference them everywhere.

A practical approach is to separate your palette from your semantic colors:

`css :root { / Palette / --blue-500: #2563eb; --blue-600: #1d4ed8; --gray-100: #f3f4f6; --gray-900: #111827;

/ Semantic / --color-primary: var(--blue-500); --color-primary-hover: var(--blue-600); --color-background: var(--gray-100); --color-text: var(--gray-900); } `

The palette layer holds the raw color values. The semantic layer maps those values to purposes. When you decide to change your primary color from blue to purple, you update one line in the semantic layer. Every button, link, and heading that uses --color-primary updates automatically.

The Color Picker helps you find the exact hex, RGB, or HSL values you need. Once you have your colors, define them as custom properties and never hard-code a hex value in a component again.

You can nest variables inside each other, which is useful for computed values:

`css :root { --shadow-color: 220 3% 15%; --shadow-elevation-low: 0 1px 2px hsl(var(--shadow-color) / 0.3); --shadow-elevation-medium: 0 4px 8px hsl(var(--shadow-color) / 0.25); } `

Changing --shadow-color updates every shadow in your design. This level of indirection would require a preprocessor in the old days, but now the browser does it for free.

Key takeaway

The most common use case for CSS variables is managing colors.

* * *

Scoping Variables to Components

Variables defined on :root are global. But you can also define variables on any selector, and they will only apply to that element and its children.

`css .card { --card-padding: 24px; --card-radius: 12px; padding: var(--card-padding); border-radius: var(--card-radius); }

.card.compact { --card-padding: 12px; --card-radius: 8px; } `

The .compact variant overrides the variables without touching any other properties. This pattern scales beautifully. You can create size variants, color variants, and density variants, all by changing a handful of variables.

Component-scoped variables also prevent naming collisions. A --padding variable on .card does not interfere with a --padding variable on .sidebar. Each component owns its own namespace.

This is where custom properties are fundamentally different from Sass variables. Sass variables are compiled away and have no runtime existence. CSS custom properties participate in the cascade, which means they respond to media queries, pseudo-classes, and JavaScript manipulation. A Sass variable cannot change when the user resizes their browser window. A CSS variable can.

* * *

Dark Mode with CSS Variables

Implementing dark mode with custom properties is straightforward. You define your light theme colors on :root, then override them inside a prefers-color-scheme: dark media query:

`css :root { --color-background: #ffffff; --color-surface: #f9fafb; --color-text: #111827; --color-text-muted: #6b7280; --color-border: #e5e7eb; }

@media (prefers-color-scheme: dark) { :root { --color-background: #0f172a; --color-surface: #1e293b; --color-text: #f1f5f9; --color-text-muted: #94a3b8; --color-border: #334155; } } `

Every element using these variables switches colors automatically. No class toggling, no JavaScript, no duplicated CSS rules. The entire theme change happens in one block of variable overrides.

If you want a manual toggle in addition to the system preference, add a data attribute:

`css [data-theme='dark'] { --color-background: #0f172a; --color-surface: #1e293b; --color-text: #f1f5f9; } `

Then toggle data-theme on the element with a small JavaScript function. This gives users control while still respecting their system setting as the default.

Use the Gradient Generator to create gradient values that work in both light and dark contexts. A gradient that looks great on a white background often needs adjusted color stops for a dark background.

Website switching between light and dark color themes
Website switching between light and dark color themes
* * *

Responsive Design with CSS Variables

Custom properties combine well with media queries for responsive adjustments that are easy to read and maintain:

`css :root { --content-width: 1200px; --spacing-section: 80px; --font-size-heading: 2.5rem; }

@media (max-width: 768px) { :root { --content-width: 100%; --spacing-section: 40px; --font-size-heading: 1.75rem; } } `

Instead of writing separate mobile styles for every component that uses these values, you update the variables once. Every heading, every section, every layout container adjusts simultaneously.

This approach keeps your media queries short and focused. Rather than repeating property declarations for dozens of selectors at each breakpoint, you override a few variables. The components themselves stay clean because they reference variables, not hard-coded values.

For production, run your stylesheet through the CSS Minifier to strip comments and whitespace. Variable names are preserved during minification because the browser needs them at runtime, but everything else gets compressed.

* * *

Manipulating Variables with JavaScript

CSS custom properties are live values in the DOM, which means JavaScript can read and write them:

`javascript // Read a variable const root = document.documentElement; const primary = getComputedStyle(root).getPropertyValue('--color-primary');

// Write a variable root.style.setProperty('--color-primary', '#7c3aed'); `

This opens up possibilities that were difficult or impossible before. You can build color pickers that update the page in real time, create scroll-driven animations by mapping scroll position to a CSS variable, or let users customize the interface through a settings panel.

A practical example is a theme customizer:

`javascript function setThemeColor(color) { document.documentElement.style.setProperty('--color-primary', color); document.documentElement.style.setProperty('--color-primary-hover', darken(color, 10)); } `

Because CSS variables cascade, setting them on the root element updates every component that references them. No class swapping, no CSS rewriting, no framework-specific state management. Just one line of JavaScript and the entire page updates.

Performance is excellent too. Updating a CSS custom property triggers only the necessary repaints. The browser does not re-parse your entire stylesheet. For most use cases, the update is visually instant.

Key takeaway

CSS custom properties are live values in the DOM, which means JavaScript can read and write them: ```javascript // Read a variable const root = document.documentElement; const primary = getComputedStyle(root).getPropertyValue('--color-primary'); // Write a variable root.style.setProperty('--color-primary', '#7c3aed'); ``` This opens up possibilities that were difficult or impossible before.

* * *

FAQ

Do CSS custom properties work in all browsers?

Yes. Custom properties have been supported in every major browser since 2017. The only browser that did not support them was Internet Explorer, which is no longer maintained. If you are building for modern browsers, there are no compatibility concerns.

Are CSS variables slower than hard-coded values?

No. The performance difference is negligible. The browser resolves variable references during style computation, which adds a tiny amount of work, but this is measured in microseconds. You would need thousands of variable lookups on a single page to notice any difference. Use them freely.

Can I use CSS variables in media query conditions?

Not directly. Media query conditions like @media (max-width: var(--breakpoint)) do not work because media queries are evaluated before the cascade resolves variables. The workaround is to use container queries or define breakpoints as fixed values. Variables work inside the declarations of a media query block, just not in the condition itself.

Should I still use Sass variables if I have CSS custom properties?

It depends on your needs. CSS custom properties are runtime values that respond to the cascade, media queries, and JavaScript. Sass variables are compile-time values that get replaced with their content during the build step. If you need theming, dynamic values, or user customization, use CSS custom properties. If you need computed values during build (like darken/lighten functions), Sass still has a role. Many projects use both.

How do I organize custom properties in a large project?

Group them by purpose in your root declaration: colors first, then spacing, then typography, then component-specific values. Use a consistent naming convention like --category-variant (for example, --color-primary, --spacing-lg, --font-heading). Keep the total number under 50 for most projects. If you exceed that, consider splitting into separate files by category.