CSS Tutorial / CSS Variables: var(), Scope, Inheritance, and Fallbacks
CSS custom properties store reusable values and are referenced with var() in property values. Their scope follows the cascade and inheritance, and a var() fallback does not automatically rescue a defined value that is invalid for the receiving property.
If you use the same accent color for multiple buttons and cards, you can group it into a meaningful variable instead of copying the color code into each selector. When changing the accent color, if you modify a declaration, the place that references that value also changes. If you make variables, even values that are only used in one place and have no separate role, the burden of keeping track of names can actually increase.
:root {
--accent: #1d5f91;
--space-unit: .5rem;
}
.button { color: white; background: var(--accent); }
.card { border: 2px solid var(--accent); padding: calc(var(--space-unit) * 3); }
Just declaring a color in the code above does not change the screen. Actual properties, such as background or border, must be referenced as var. If the name indicates the role, such as --accent, rather than fixing the current color, such as --blue, it will be less confusing when changing to a different color later. --accent and --Accent are different names, so match the case and spelling.
var() is used for the value of properties such as color or padding. Selectors or property names cannot be replaced with variables, and a breakpoint cannot be determined by putting @media (min-width: ...) or var() in the condition value of a container size query. Write breakpoints directly in the corresponding conditional statement, and distinguish them from values to which variables can be applied, such as the color or spacing of elements.
Understand root, inheritance, and component scope
In an HTML document, :root matches the top-level HTML element, so it is convenient to have a common default value in the document. Common -- custom properties are inherited by descendants. Values declared within a card can be used by descendants, such as the title, but are not passed on to unrelated sibling cards.
.card { --card-accent: var(--accent); }
.card.warning { --card-accent: #a63e32; }
.card { border-color: var(--card-accent); }
.card h2 { color: var(--card-accent); }
This rule is a component part of the full example below. .card.warning matches cards with both classes. Winning or losing is not determined by the listing order of HTML classes, but by matching the CSS Inheritance, Specificity, and the Cascade to determine the value. Changing the global accent will change the default card, and warning cards with their own card-accent will retain their original warning colors.
When combining variables with other variables, it is also important which element the value is calculated from. If --line-color: var(--accent) is calculated in the parent and then passed on to the child, the already inherited line-color will not always be recalculated just because the accent is overwritten in the child. Derived values that should follow the accent of their descendants are obviously better declared in the element or directly referencing the accent in the actual attribute. For definitions of scope and inheritance, see CSS custom property specification.
Distinguish fallbacks and invalid computed values
var(--notice-color, #6a3989) provides a purple fallback value when a variable value is missing and cannot be used. Even if a variable is defined and has a meaningful value in other properties, such as 20px, if the value does not match color, the color declaration becomes invalid at the time of calculation. In this case, you should not expect to be “replaced by purple”.
| variable state | Result of color declaration | Colors for the entire example |
|---|---|---|
| No notice-color | Using fallback values for var | purple |
| notice-color is red | Use defined values | red |
| notice-color is 20px | Inheritance/initial value rules applied because it does not match the color | Because color inherits, use the parent's green text color. |
As shown below, even if there is a blue declaration first in the same rule, if the value after variable substitution in the second declaration does not match, it will not return to the first blue. After the declaration selected in the cascade is invalid at the time of evaluation, color's inheritance rules are applied. In the same situation, a property that is not inherited may be treated as the initial value of that property. MDN Custom Property Guide explains the difference between replacement and invalid values.
.sample {
color: #1d5f91;
color: var(--notice-color, #6a3989);
}
.sample.bad { --notice-color: 20px; }
You can also put other vars in the fallback value. For example, var(--button-accent, var(--accent, #1d5f91)) looks for the value to use, starting with the button-specific value, then the common value, and finally the primary color. However, if the uses of numbers, lengths, and colors are mixed, the same problem occurs, so variable names and usage must be managed together.
Combine values and units with calc()
The example multiplies --space-unit: .5rem by 3 to set card padding. rem uses the root font size, so changing that size changes the spacing. Use calc() for the calculation rather than concatenating strings, and leave spaces around addition and subtraction operators. For further detail, see CSS Colors and Units: px, em, rem, and Percentages.
:root {
--space-unit: .5rem;
--space-card: calc(var(--space-unit) * 3);
}
.card { padding: var(--space-card); }
Consistently choose whether variables include units or just numbers. If you enter a unitless value such as 2 in the length variable, it may not be suitable for applications that require units. Avoid combinations of different uses, such as applying calc to a variable containing a color. For the basic syntax of the formula, refer to the MDN calc document.
Run the scope and fallback comparison
Save the following code as custom-properties.html in UTF-8 and open it in your browser. Compare the accent colors of the two cards and the text colors of the three sentences. The meaning of the colors was also written in letters so that the results could not be distinguished by color alone.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS variable scope and substitution values</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
:root { --space-unit: .5rem; --space-card: calc(var(--space-unit) * 3); --accent: #1d5f91; }
.cards { display: grid; gap: 1rem; }
.card { --card-accent: var(--accent); padding: var(--space-card); border: 2px solid var(--card-accent);
border-radius: .5rem; background: #f6f9fb; }
.card h2 { color: var(--card-accent); margin-block: 0 .5rem; }
.card p { margin: 0; }
.card.warning { --card-accent: #a63e32; }
.fallback-area { margin-block: 2rem; padding: 1rem; border: 1px solid #8a9ba7; color: #28633b; }
.sample { color: #1d5f91; color: var(--notice-color, #6a3989); }
.sample.bad { --notice-color: 20px; }
.sample.good { --notice-color: #a63e32; }
</style>
</head>
<body>
<main>
<h1>Variable scope and fallbacks</h1>
<div class="cards">
<article class="card"><h2>Basic card</h2><p>The heading and border share the same accent color.</p></article>
<article class="card warning"><h2>warning card</h2><p>The accent color changes only inside this card.</p></article>
</div>
<section class="fallback-area">
<h2>The parent's text color is green</h2>
<p class="sample missing">Undefined: purple fallback</p>
<p class="sample bad">20px: Doesn't match the color, so it inherits the green color from the parent.</p>
<p class="sample good">Defined as red: use that color</p>
</section>
</main>
</body>
</html>
- Make sure that the title and border of the basic cards are blue and that the warning cards are red. The three sentences below are colored purple, green, and red, respectively.
- Change only the root accent to
#28633b. The default card's heading and border should change, while the warning card keeps its own warning color. Restore the root accent to#1d5f91after checking. - Replace only space-unit with
1rem. The paddings of both cards stretch together. After confirmation, restore to .5rem. - Change only the notice-color of bad from
20pxto#1d5f91. The sentence will change from green to blue. After confirmation, restore it to 20px. - Delete only the notice-color declaration of bad. This time a purple fallback is used. Compare with the blue result from the previous step and restore the original declaration.
Diagnose missing or incorrect variables
- In developer tools, check if the variable name and value actually exist in that element. Look at spelling, capitalization, and declaration scope first.
- Check whether descendants or other rules overwrite the same variable. It may not be an error for a component to retain its unique values even if its common value changes.
- Check whether the variable is referenced in the actual property and whether the units and type of the substituted value are correct.
- If a value is created through multiple variables, check the calculated element and inherited value. Also avoid declarations that refer to the same variable as themselves or that refer circularly.
You can take advantage of variables by distinguishing between common colors and spacing of the design and exceptions for each component, and checking the results before and after the change. Rather than increasing the number of variable names, making it clear which values are shared in which scope is more helpful for maintenance.










