An CSS variable is a named, reusable value that we can use in different rules within our stylesheet.
Its official name is Custom Properties, but almost everyone calls them CSS variables. They are used to avoid repeating the same color, spacing, font, or size over and over across the project.
Think of a corporate color like #0055ff. We use it in buttons, links, borders, icons, and headings. One day someone decides that blue should be “a bit greener, fresher, more startup that does AI stuff”. If you’ve written it by hand in fifty places, you’ll have to search and replace everywhere.
With CSS variables, you change the value in one single place and everything that depends on it updates automatically.
What to Store in Variables
Not everything deserves to become a variable. If a value appears only once and doesn’t represent a design decision, it can probably stay as is.
- Primary colors and states:
--color-primary,--color-error,--color-success. - Typography:
--font-base,--font-headings. - Spacing:
--space-xs,--space-sm,--space-md. - Radii:
--radius-sm,--radius-md. - Shadows:
--shadow-card,--shadow-modal. - Durations:
--duration-fast,--duration-normal.
Declaring Variables
CSS variables are declared with a name that must start with a double hyphen (--).
:root {
--color-primary: #0055ff;
--color-secondary: #ff4500;
--font-base: "Helvetica Neue", Arial, sans-serif;
--spacing-medium: 1rem;
}
The :root pseudo-class targets the document’s root element, which in HTML corresponds to <html>. If we define variables there, they become available for the entire page.
Give names that explain intent, not appearance. --color-error is usually better than --red, because tomorrow the error might not be exact red, but maroon, coral, or whatever.
Using Variables with var()
To read a variable we use the var() function.
.primary-button {
background-color: var(--color-primary);
padding: var(--spacing-medium);
font-family: var(--font-base);
}
.highlighted-link {
color: var(--color-primary);
}
Now the actual value lives in :root, and the rules only say what visual intent they want to use. If tomorrow we change this:
:root {
--color-primary: #008c7a;
}
All elements using var(--color-primary) will adopt the new color.
Variables and Cascade
CSS variables participate in the cascade. This means they can change depending on the selector, context, or inheritance.
:root {
--accent-color: #2563eb;
}
.card {
border-top: 4px solid var(--accent-color);
}
.card.alert {
--accent-color: #dc2626;
}
.card.success {
--accent-color: #16a34a;
}
The .card rule doesn’t change. It always uses --accent-color. What changes is the variable’s value depending on the class the card has.
This pattern is very powerful for components because it allows the same visual structure to adopt variations without duplicating all the CSS.
Variable Scope
A variable defined in :root is global. A variable defined inside a selector only exists for that selector and its descendants.
.panel {
--panel-background: #ffffff;
--panel-border: #e5e7eb;
background: var(--panel-background);
border: 1px solid var(--panel-border);
}
.panel.highlighted {
--panel-background: #eff6ff;
--panel-border: #93c5fd;
}
This allows creating small “local configurations”. The component behaves like a machine that receives values from the outside and applies them internally.
Not all variables need to be global. If a value only makes sense inside .panel, declaring it inside .panel makes the CSS easier to read and maintain.
Creating Themes and Dark Mode
One of the most illustrative use cases for CSS variables is seeing how to use them to create themes. We define stable names (--background, --text, --border) and change their values depending on the context.
:root {
--background: #ffffff;
--text: #1f2937;
--border: #e5e7eb;
}
body.dark-mode {
--background: #111827;
--text: #f9fafb;
--border: #374151;
}
.card {
background: var(--background);
color: var(--text);
border: 1px solid var(--border);
}
The component’s CSS doesn’t need to know if it’s in light or dark mode. It only consumes variables. The theme is decided in another layer.
We can also react to the operating system’s preference.
@media (prefers-color-scheme: dark) {
:root {
--background: #111827;
--text: #f9fafb;
--border: #374151;
}
}
