CSS Variables: Custom Properties, Inheritance, and Fallbacks
Master CSS custom properties. Learn how to declare variables, understand scopes, manage fallback values, and update themes dynamically.
Introduction
In large web applications, stylesheets can quickly grow to thousands of lines. If you use static hexadecimal colors (#3b82f6) or layout widths (250px) directly throughout your code, maintaining consistency becomes difficult. Changing your brand's primary color would require searching and replacing values across dozens of stylesheets, which can easily introduce bugs.
To solve this, modern CSS provides Custom Properties (commonly known as CSS Variables). CSS Variables allow you to store styling values in one place and reuse them throughout your stylesheet. Because CSS Variables are dynamic—meaning they inherit values down the document tree and can be updated using JavaScript on the fly—they are essential to building dark mode toggles and modern design systems.
What You Will Learn
- How to declare global variables using the
:rootselector. - Accessing variables and defining fallback values with
var(). - The differences between Global Scope and Local Scope.
- Dynamic theme switching using JavaScript and CSS variables.
- How CSS variables differ from SASS/SCSS variables.
Prerequisites
Detailed Explanation: Variable Scope and Inheritance
Like variables in other programming languages, CSS variables have a scope that determines where they can be accessed.
1. Global Variables (:root)
To make a variable accessible across your entire stylesheet, declare it inside the :root pseudo-class. The :root selector targets the highest level parent in the document tree (the <html> tag):
- Declaration Syntax: Variable names MUST start with two dashes (
--). - Access Syntax: Use the
var()function, passing the variable name as the parameter.
:root {
--primary-color: #3b82f6; /* Declared globally */
--base-padding: 16px;
}
.button {
background-color: var(--primary-color);
padding: var(--base-padding);
}
2. Local Variables (Element Scopes)
If you declare a variable inside a specific component selector, it is scoped locally. It can only be accessed by that element and its child elements.
.card {
--card-bg: #f8fafc; /* Scoped locally to .card and its children */
background-color: var(--card-bg);
}
.sidebar {
/* Error: var(--card-bg) will not render here as it is out of scope */
background-color: var(--card-bg);
}
3. Variable Fallback Values
If a variable is not defined (for example, if it fails to load), you can specify a fallback value inside the var() function as a second parameter:
.badge {
/* Uses --accent-color if defined; otherwise, falls back to #10b981 */
color: var(--accent-color, #10b981);
}
CSS Variables vs. Preprocessor Variables (SASS)
| Feature | CSS Variables (Native) | SASS / SCSS Variables | | :--- | :--- | :--- | | Compilation | Native browser support. Evaluated at runtime. | Compiled into static CSS values during build time. | | DOM Awareness | Aware of the DOM structure. Inherits values down the tree. | Unaware of the DOM. Static compiler values. | | JS Access | Can be read and modified dynamically via JavaScript. | Cannot be accessed or modified at runtime. | | Media Queries | Can change values inside media queries dynamically. | Cannot change values inside media queries. |
Visual Learning Diagram (Mermaid)
graph TD
Root[Global :root --primary-color: blue] --> ParentCard[Component .card --primary-color: green]
Root --> ParentSidebar[Component .sidebar]
ParentCard --> ChildButton[.card-button inherits green]
ParentSidebar --> ChildSidebarLink[.sidebar-link inherits blue]
Code Examples
Example 1: Clean Global Design System Palette
Establish a global palette that is easy to update:
:root {
/* Brand Theme Colors */
--color-primary: #3b82f6;
--color-primary-dark: #1d4ed8;
--color-success: #10b981;
--color-dark: #0f172a;
/* Typography Variables */
--font-sans: 'Inter', sans-serif;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
/* Layout Sizing Constants */
--border-radius-lg: 12px;
--border-radius-md: 8px;
}
body {
font-family: var(--font-sans);
background-color: var(--color-dark);
color: #ffffff;
}
.button-success {
background-color: var(--color-success);
border-radius: var(--border-radius-md);
font-size: var(--font-size-sm);
}
Example 2: Dynamic Dark Mode Toggle (JS Integration)
Redefine theme variables inside a class name, and toggle that class using JavaScript:
/* Base Light Theme */
:root {
--bg-color: #ffffff;
--text-color: #0f172a;
--card-bg: #f8fafc;
}
/* Dark Theme Overrides */
[data-theme="dark"] {
--bg-color: #0f172a;
--text-color: #f8fafc;
--card-bg: #1e293b;
}
/* Styles automatically update when variable values change */
body {
background-color: var(--bg-color);
color: var(--text-color);
}
.card {
background-color: var(--card-bg);
}
Using JavaScript to switch themes dynamically:
// Toggle theme attribute on the document root
const toggleTheme = () => {
const currentTheme = document.documentElement.getAttribute('data-theme');
const targetTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', targetTheme);
};
Best Practices & Common Mistakes
- Incorrect Fallback Syntax: A common mistake is writing multiple variables inside a single fallback incorrectly (e.g.
var(--color-a, --color-b)). The fallback must be a valid property value, or anothervar()function nested inside:var(--color-a, var(--color-b, #fff)). - Use Semantic Names: Avoid naming variables based on exact values (like
--blue: #0000ff). If you change your brand theme later, a blue variable containing a green color is confusing. Use semantic names instead (e.g.--color-primary).
FAQs
Q: Can I use CSS variables in media queries? A: Yes! You can redefine variable values inside media queries. This is a best practice for adjusting responsive layout gaps:
:root { --grid-gap: 10px; }
@media (min-width: 768px) { :root { --grid-gap: 20px; } }
.grid { gap: var(--grid-gap); }
Q: Are CSS variables supported by all browsers? A: Yes. CSS custom properties are supported by all modern browsers (over 99% global support).
Summary
CSS variables (custom properties) simplify theme management. Declared globally inside the :root selector or scoped locally, variables inherit values down the DOM tree, support fallback overrides, and can be adjusted dynamically at runtime using JavaScript.
Related Articles
Next Tutorial
How do we use mathematical operations to calculate fluid values? Let's check: Modern CSS Math Functions.