CSS Custom Properties and var()
Understand author-defined custom properties, inheritance, cascade participation, var() substitution, fallbacks, guaranteed-invalid values, dependency cycles, and the boundary to typed property registration.
Intermediate
Custom properties add author-defined properties to CSS. This lesson owns the custom-property and var() model: declaration, inheritance, cascade participation, permissive token storage, substitution, fallbacks, guaranteed-invalid values, dependency cycles, and the point at which substituted values are checked against the consuming property's grammar. The Cascade remains the owner of deciding which declaration wins; Values and Units remains the owner of the general value grammar.
A custom property is still a CSS property
A custom property name starts with two hyphens, such as --accent. Custom properties apply to elements and pseudo-elements, inherit by default, and participate in the ordinary cascade. They are not a separate configuration language layered on top of CSS: the cascade selects their value just as it selects other properties.
Output
Ready to run.
Quick check
What primarily decides which competing declaration of --accent wins?
Custom property names are case-sensitive
Unlike ordinary CSS property names, custom property names are case-sensitive. --Accent and --accent are different custom properties. The distinction is part of the custom-property model and must be preserved when names are authored or accessed.
Output
Ready to run.
Quick check
Are --Accent and --accent the same custom property?
Custom property values are deliberately permissive
An unregistered custom property accepts a very broad token stream as its value. CSS does not generally interpret that value according to the grammar of the property that will eventually consume it. This lets authors store values for later substitution, but it also means a custom property can hold tokens that are useless for any particular consuming property.
Output
Ready to run.
Quick check
When is an unregistered custom property's stored value generally checked against the grammar of a consuming property?
var() substitutes a custom property value
The var() function can appear in a property value and names the custom property whose value should be substituted. Its first argument is the custom-property name. Substitution operates on CSS tokens, not by concatenating arbitrary text into a new token.
Output
Ready to run.
Quick check
Where can var() be used?
Fallbacks handle an unavailable variable
A var() function may provide a fallback after its custom-property name. The fallback is used when the referenced custom property has its initial guaranteed-invalid value. The fallback is itself a value and can contain nested var() references.
Output
Ready to run.
Quick check
What does the second argument of var(--name, fallback) provide?
The guaranteed-invalid value is not the same as an empty value
An unregistered custom property starts with a guaranteed-invalid value. A var() reference with no usable value and no fallback can therefore make the consuming declaration invalid at computed-value time. An explicitly empty custom-property value, such as --flag: ;, is different from the guaranteed-invalid initial value.
Output
Ready to run.
Quick check
What happens to a var() reference with no valid custom-property value and no fallback?
Inheritance happens before a descendant can override its own value
Because custom properties inherit by default, a descendant can use a value defined on an ancestor until it defines the same custom property itself. A var() reference uses the computed value of the associated custom property on the element where the reference is evaluated. This is why custom-property themes can flow through a component tree.
Output
Ready to run.
Quick check
If an ancestor defines --accent and a descendant does not redefine it, what normally happens?
Substitution is token-based, not string concatenation
var() substitution does not let authors manufacture a different CSS token by placing a variable next to another token. For example, a number stored in --gap cannot be turned into a length by writing var(--gap)px. Use CSS value operations such as calc() when the resulting grammar requires a typed value.
Output
Ready to run.
Quick check
Why does `margin-top: var(--n)px` fail when --n is `20`?
Custom-property dependency cycles invalidate the cycle
Custom properties can refer to one another through var(). For each element, the dependencies form a graph. If the graph contains a cycle, the custom properties in that cycle become invalid at computed-value time. A fallback in a separate reference does not magically break a cycle inside the participating custom-property definitions.
Output
Ready to run.
Quick check
What is the defining problem when --a uses var(--b) and --b uses var(--a) on the same element?
Custom properties do not create a second cascade
Custom properties are integrated with the cascade rather than replacing it. If several rules declare the same custom property, the Cascade owner determines the winning declaration. The Custom Properties owner then explains what happens when the winning value is inherited or substituted with var().
Output
Ready to run.
Quick check
Which curriculum owner explains which competing --tone declaration wins?
Registration is a related, separate typed-property mechanism
CSS Properties and Values API defines registration mechanisms such as @property that can give a custom property an explicit syntax, inheritance behavior, and initial value. That registration model is distinct from the permissive unregistered custom-property model taught here. This lesson uses registered properties only to establish the boundary; it does not turn the Custom Properties lesson into a full CSS Properties and Values API curriculum.
The live CSS Custom Properties Level 1 specification remains the primary stable source for the --* and var() model. CSS Custom Properties Level 2 is an Editor's Draft and extends the module; developing additions are tracked as such rather than presented as settled fundamentals.
A custom-property debugging sequence
When var() produces an unexpected result, inspect the problem in order: find the winning custom-property declaration, check inheritance on the element, inspect the stored token stream, check var() fallbacks, look for dependency cycles, and finally check whether the substituted value satisfies the consuming property's grammar. This separates cascade problems from substitution problems and from ordinary property-value errors.
Output
Ready to run.
Quick check
What should you inspect first when two rules appear to provide different values for the same custom property?
Ownership rule: use this lesson for custom-property definitions, inheritance, var() substitution, fallbacks, guaranteed-invalid values, dependency cycles, and the boundary to typed registration. Use the Cascade for conflict resolution, Values and Units for general value grammar, Conditional Rules for conditions such as @media, and Properties and Values API specifications for the deeper registration/typed-property model.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.