Learn how to define and consume custom properties using the var() function to create maintainable, themeable CSS.
What it is
CSS Custom Properties (often called CSS Variables) are user-defined values that can be reused throughout a document. The var() function is used to access these values. Unlike preprocessor variables (like in Sass or Less), CSS custom properties are dynamic and participate in the cascade, meaning they can change based on context, media queries, or JavaScript interactions without requiring a rebuild.
The mental model is simple: you declare a value once with a name starting with two dashes (--name), and then retrieve it anywhere using var(--name).
Why it matters
- Maintainability: Change a color or spacing unit in one place, and it updates everywhere.
- Theming: Easily switch between light and dark modes by overriding root variables.
- Dynamic Values: React to viewport size or user preferences via media queries.
- Fallbacks: Provide default values if a variable is undefined, preventing broken styles.
Syntax or steps
- Define: Create a custom property inside a selector (usually
:rootfor global scope). Syntax:--property-name: value; - Consume: Use the
var()function as a value for any standard CSS property. Syntax:property: var(--property-name); - Fallback (Optional): Add a second argument to
var()for safety. Syntax:property: var(--property-name, fallback-value);
Example
:root {
--primary-color: #185FA5;
--spacing-unit: 8px;
}
.btn {
background-color: var(--primary-color);
padding: calc(var(--spacing-unit) * 2);
border: none;
color: white;
}
/* Dark mode override */
@media (prefers-color-scheme: dark) {
:root {
--primary-color: #4A90E2;
}
}
Explanation: We define --primary-color and --spacing-unit globally. The .btn class uses var() to apply the blue background and calculates padding dynamically. If the user prefers dark mode, the media query overrides --primary-color, instantly changing all buttons without touching the .btn rule itself.
Common mistakes
- Missing Dashes: Writing
var(primary-color)instead ofvar(--primary-color). Always include the double dash. - Scope Issues: Defining a variable inside a specific class but trying to use it outside that scope. Variables inherit from their parent selectors.
- No Fallback: Using
var(--undefined-var)results in an invalid property value, which may cause the browser to ignore the entire declaration. Always provide a fallback for critical styles. - Case Sensitivity: CSS custom properties are case-sensitive.
--Brandand--brandare different variables.
When to use it
| Feature | CSS Custom Properties (var()) |
Preprocessor Variables (Sass/Less) |
|---|---|---|
| Runtime Changes | Yes (via JS or Media Queries) | No (compiled away) |
| Inheritance | Participates in DOM tree | Static text replacement |
| Best For | Themes, dynamic layouts, accessibility | Math operations, mixins, static config |
Use var() when you need values that might change after the page loads or depend on context. Use preprocessor variables for static design tokens that never change at runtime.
Practice
Guided Exercise: Define a variable --font-size-base set to 16px. Apply it to the body tag. Then, create a class .large-text that uses calc() to make the font size 1.5 times the base variable.
Challenge: Create a button style where the background color changes on hover using a variable named --hover-bg. Ensure there is a fallback color if --hover-bg is not defined.
Hint: Your hover rule should look like background: var(--hover-bg, #ccc);
Quick check
Q: What happens if you reference a custom property that has not been defined and no fallback is provided?
A: The property becomes invalid, and the browser ignores that specific declaration, potentially leaving the element unstyled or using inherited values.
Summary
The var() function enables dynamic, reusable styling through CSS custom properties. By defining values in scopes like :root and consuming them with fallbacks, you build resilient themes that adapt to user preferences and device contexts efficiently.