CSS Variables: Writing Smarter, More Maintainable Stylesheets

CSS variables, or custom properties, enable reusable styling in stylesheets by allowing single updates to propagate across elements. They support global and local scopes, fallbacks, and dynamic theming for responsive design.

What CSS Variables Are

CSS variables, also known as custom properties, let you define a value once and reuse it anywhere in your stylesheet. Instead of searching for every instance of a color or spacing value to update, you change it in one place and everything referencing it updates automatically.

Variables always start with two dashes, such as --brand-color or --base-spacing. You apply them using the var() function.

:root {
--brand-color: #3b82f6;
--base-font-size: 16px;
}
button {
background-color: var(--brand-color);
font-size: var(--base-font-size);
}

Every button on the page shares the same color and size. Changing --brand-color once updates all of them instantly.

Declaring Variables

Global variables with :root

The :root pseudo-class targets the highest-level element in the document, making any variable declared inside it available everywhere. It is the standard place for design tokens: the colors, fonts, and spacing values that define a consistent look across a site.

:root {
--surface: #f9fafb;
--text-primary: #111827;
--accent: #6366f1;
--radius: 8px;
}

Local variables for components

Variables can be scoped to a specific selector. One declared inside .card applies only to that element and its children, keeping component-specific values self-contained.

.card {
--card-gap: 24px;
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: var(--card-gap);
box-shadow: var(--card-shadow);
}

Using Variables with var()

To apply a variable, pass its name to var().

body {
background: var(--surface);
color: var(--text-primary);
}
a {
color: var(--accent);
}

Fallback values

var() accepts an optional second argument used when the variable is not defined.

.badge {
background: var(--badge-color, #6366f1);
color: var(--badge-text, white);
}

If --badge-color has not been declared, the element falls back to #6366f1. Fallbacks are especially useful when building reusable components that may be dropped into a page without a full set of variables defined.

Dynamic Variables

Theming with light and dark mode

Because variables can be overridden at different scopes, implementing a theme switch only requires redefining the variable values, not rewriting any of the property declarations that use them.

.light-mode {
--surface: #ffffff;
--on-surface: #111827;
}
.dark-mode {
--surface: #1f2937;
--on-surface: #f9fafb;
}
body {
background: var(--surface);
color: var(--on-surface);
}

Toggling a class on the body element switches the entire theme. The background and color declarations never need to change.

Updating variables with JavaScript

CSS variables live in the DOM and can be changed at runtime using setProperty.

document.documentElement.style.setProperty('--accent', '#f59e0b');

This updates --accent globally, affecting every element that references it. The change is immediate and requires no page reload. It is the mechanism behind live theme pickers, animated color transitions, and any interface that adapts to user input.

Variables in media queries

Variables can be redefined inside a media query to adapt a design to different screen sizes without duplicating the property declarations.

:root {
--body-size: 16px;
--heading-size: 32px;
}
@media (max-width: 600px) {
:root {
--body-size: 14px;
--heading-size: 22px;
}
}
p { font-size: var(--body-size); }
h1 { font-size: var(--heading-size); }

Both elements scale on smaller screens without any additional rules targeting them directly.

Best Practices

Declare global design tokens in :root and use component-level variables for values that belong to a specific block. Always provide fallback values for variables that may not be defined, particularly in reusable components intended to work in isolation. Use variables inside media queries to keep responsive adjustments centralized rather than scattered across individual selectors.

Avoid reaching for a variable every time you need a one-off value. Variables add the most value when the same piece of information would otherwise appear in multiple places. If a value is used only once, a direct declaration is simpler and easier to read.

See you soon.

View Comments (3)

Leave a Reply

Subscribe to My Newsletter

Subscribe to my email newsletter to get the latest posts delivered right to your email. Pure inspiration, zero spam.

Discover more from Discuss Data Science, Machine Learning and Analytics

Subscribe now to keep reading and get access to the full archive.

Continue reading