Introduction to CSS Custom Properties
CSS Custom Properties (CSS Variables)
CSS Custom Properties, commonly known as CSS variables, allow you to define reusable values throughout your stylesheet using the -- prefix. Unlike preprocessor variables (Sass, Less), CSS custom properties are natively supported in the browser and can be dynamically changed at runtime using JavaScript or CSS itself.
Why CSS Variables Matter
CSS variables solve a long-standing problem in web development: maintaining consistent design tokens across large stylesheets. Before CSS variables, changing a primary color meant searching and replacing every instance manually. With CSS custom properties, you define the value once and reference it everywhere.
:root {
--primary-color: #2563eb;
--spacing-unit: 8px;
--font-size-base: 16px;
}
.button {
background-color: var(--primary-color);
padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
font-size: var(--font-size-base);
}
Key Concepts
- Declaration: Variables are declared with
--prefix inside a selector (e.g.,--primary-color: #2563eb;) - Reference: Variables are accessed using the
var()function (e.g.,var(--primary-color)) - Scope: Variables inherit down the DOM tree, so a variable set on
:rootis available everywhere - Fallback:
var()accepts a second argument as a fallback value (e.g.,var(--missing, #000))
When to Use CSS Variables
Use CSS custom properties when you need design tokens that may change based on context, theme, or user preference. They are essential for dark mode implementations, component libraries, and any design system that requires runtime flexibility.
Common Pitfalls
Avoid declaring variables on high-specificity selectors where they become difficult to override. Also, remember that CSS custom properties are not replaced at compile time—they resolve in the browser, which can affect performance in deeply nested scenarios.
Dynamic Theming with CSS Variables
Implementing Dynamic Theming
CSS custom properties excel at creating dynamic theming systems. By defining variables on a parent element and overriding them on children or state changes, you can create powerful theme switching without JavaScript re-rendering.
Dark Mode Implementation
The most common use case is implementing dark mode. By toggling a class on the html or body element, you can redefine all color variables at once:
:root {
--bg-primary: #ffffff;
--bg-secondary: #f3f4f6;
--text-primary: #111827;
--text-secondary: #6b7280;
--border-color: #e5e7eb;
}
.dark-theme {
--bg-primary: #111827;
--bg-secondary: #1f2937;
--text-primary: #f9fafb;
--text-secondary: #9ca3af;
--border-color: #374151;
}
body {
background-color: var(--bg-primary);
color: var(--text-primary);
}
.card {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
}
Component-Level Theming
Variables can be overridden on specific components to create variant themes without affecting the global context:
.alert {
--alert-bg: #fef2f2;
--alert-text: #991b1b;
--alert-border: #fca5a5;
background-color: var(--alert-bg);
color: var(--alert-text);
border: 1px solid var(--alert-border);
padding: var(--spacing-md);
border-radius: var(--radius-sm);
}
.alert--success {
--alert-bg: #f0fdf4;
--alert-text: #166534;
--alert-border: #86efac;
}
Media Query Theming
CSS variables can also respond to media queries for responsive theming:
:root {
--content-max-width: 100%;
--font-size-heading: 1.5rem;
--spacing-layout: 1rem;
}
@media (min-width: 768px) {
:root {
--content-max-width: 720px;
--font-size-heading: 2rem;
--spacing-layout: 2rem;
}
}
Best Practices for Theming
Always define your base theme tokens on :root so they cascade to all elements. Use semantic variable names (e.g., --color-surface) rather than literal color values (e.g., --white) to make theme switching intuitive. Keep your variable namespacing consistent with prefixes like --color-, --spacing-, and --font-.
Design Tokens and Scalable Systems
Building Design Systems with CSS Variables
Design tokens are the foundational values of a design system—colors, typography, spacing, shadows, and breakpoints that establish visual consistency. CSS custom properties are the ideal mechanism for managing these tokens in a browser-native way.
Token Architecture
Organize your tokens into a layered architecture: primitive tokens, semantic tokens, and component tokens:
/* Primitive tokens - raw values */
:root {
--blue-50: #eff6ff;
--blue-100: #dbeafe;
--blue-500: #3b82f6;
--blue-600: #2563eb;
--blue-900: #1e3a8a;
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-900: #111827;
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
}
/* Semantic tokens - purpose-driven */
:root {
--color-primary: var(--blue-600);
--color-primary-hover: var(--blue-900);
--color-surface: var(--gray-50);
--color-text: var(--gray-900);
--color-border: var(--gray-100);
--spacing-element: var(--spacing-md);
--spacing-section: var(--spacing-xl);
}
/* Component tokens - component-specific */
.button {
--button-bg: var(--color-primary);
--button-text: #ffffff;
--button-radius: var(--radius-md);
--button-padding: var(--spacing-sm) var(--spacing-md);
}
Typography Scales
CSS variables enable fluid typography that scales smoothly across breakpoints:
:root {
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--font-family-sans: 'Inter', system-ui, sans-serif;
--font-family-mono: 'Fira Code', monospace;
--font-weight-normal: 400;
--font-weight-semibold: 600;
--line-height-tight: 1.25;
--line-height-normal: 1.5;
}
Shadow and Elevation Systems
Define a consistent elevation system using layered shadows:
:root {
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
}
Runtime Token Modification
One of the most powerful features of CSS custom properties is the ability to modify tokens at runtime via JavaScript:
// Change spacing scale dynamically
document.documentElement.style.setProperty('--spacing-md', '20px');
// Respond to user preferences
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
if (prefersDark.matches) {
document.documentElement.classList.add('dark-theme');
}
This approach allows you to create adaptive interfaces that respond to user preferences, accessibility settings, or contextual changes without page reloads.
Quiz
1. What prefix is used to declare a CSS custom property?
2. How do you provide a fallback value when using a CSS custom property?
3. What is the main advantage of CSS custom properties over Sass/Less variables?
Flashcards
Question
What is the syntax for declaring a CSS custom property?
Click to reveal answer
Answer
CSS custom properties are declared with a double-hyphen prefix inside a selector: `--property-name: value;`. Example: `:root { --primary-color: #3b82f6; }`. They are then referenced using the `var()` function: `color: var(--primary-color);`.
Question
How do CSS custom properties enable dynamic theming?
Click to reveal answer
Answer
CSS custom properties cascade down the DOM tree, so overriding a variable on a parent element (like toggling a `.dark-theme` class) automatically updates all child elements that reference it. This allows instant theme switching without recalculating or replacing values.
Question
What is the purpose of design tokens in CSS?
Click to reveal answer
Answer
Design tokens are named values (colors, spacing, typography, shadows) stored as CSS custom properties that establish a consistent visual language. They create a single source of truth for design decisions, making it easy to update the entire design system by changing a few token values.
Revision Notes
Key Takeaways
- 1. CSS custom properties use the `--` prefix for declaration and `var()` for reference, with optional fallback values as a second argument
- 2. Variables cascade and inherit down the DOM tree, making them ideal for component-level and theme-level overrides
- 3. Define primitive tokens (raw values), semantic tokens (purpose-driven), and component tokens for scalable design systems
- 4. CSS variables can be modified at runtime via JavaScript, enabling dynamic theming, user preferences, and responsive adjustments
- 5. Always use semantic naming conventions (e.g., `--color-primary`) rather than literal values (e.g., `--blue-500`) for maintainability
Interview Tips
- • Explain the difference between CSS custom properties and preprocessor variables—the key distinction is runtime dynamic modification vs. compile-time static replacement
- • Describe a dark mode implementation strategy using CSS variables on `:root` with a `.dark-theme` class override
- • Discuss performance considerations: CSS variables resolve in the browser, so avoid excessive re-computation in deeply nested selectors
- • Know how to use `var()` with fallback values and how variables cascade through the DOM tree for component scoping
- • Be prepared to explain design token architecture: primitive, semantic, and component-level tokens
Cheat Sheet
Declare: --var-name: value; | Reference: var(--var-name, fallback) | Scope: Cascades down DOM tree | Runtime: Change via element.style.setProperty('--var', val) | Theming: Override on .dark-theme or media query | Tokens: Use --color-*, --spacing-*, --font-* namespaces