Invalid CSS Custom Properties at Computed-Value Time
When a CSS custom property resolves to an invalid value for the
property it is applied to, the browser cannot catch the error during the
initial parsing phase. Instead, the declaration becomes "invalid at
computed-value time" (IACVT). When this occurs, CSS does not fall back
to previously declared values in the cascade; instead, it immediately
treats the property as though it were explicitly set to
unset, resulting in either its inherited value or its
initial default.
Why Standard Fallbacks Fail
In traditional CSS, the browser discards invalid property-value pairs
during the parse phase. For example, if you write
color: 20px;, the parser recognizes that a pixel length is
not a valid color and ignores the line entirely, allowing any earlier
valid declaration in the cascade to take precedence.
Custom properties work differently. Because a custom property can
hold virtually any token stream, the browser considers a declaration
like color: var(--theme-color); completely valid at parse
time. The browser cannot determine whether the value is acceptable until
it evaluates the substitution during computed-value time.
By the time computed-value time occurs, the cascade has already finished. The browser has already decided which rule won the cascade, meaning it cannot jump back up the stylesheet to pick a previously overwritten declaration.
The "Invalid at Computed-Value Time" Process
When substitution occurs and the value does not match the formal syntax of the target property, the browser triggers the IACVT mechanism:
- Substitution Check: The browser evaluates the
var()function and replaces it with the custom property's tokens. - Validation: The resolved value is checked against the target CSS property's allowed syntax.
- Invalidation: If the syntax is incompatible (e.g.,
passing
16pxtobackground-color), the declaration fails validation. - Resolution via
unset: The browser replaces the entire value with theunsetkeyword.
The Resulting Value: Initial vs. Inherited
Because the property is treated as unset, its final
appearance depends directly on whether the property is naturally
inheritable:
- Inherited Properties (e.g.,
color,font-family): The element inherits the computed value from its parent element, just as if you had declaredcolor: inherit;. - Non-Inherited Properties (e.g.,
background-color,margin,display): The element reverts to the CSS specification's defaultinitialvalue. For instance,background-colorreverts totransparent.
Example
Consider the following CSS:
:root {
--accent-color: 42px; /* Invalid value for a color */
}
.card {
background-color: blue;
background-color: var(--accent-color);
}A developer might expect .card to display a
blue background because the second declaration fails.
However:
- Both declarations are parsed. The second
background-colorwins the cascade because it is valid syntax at parse time. - At computed-value time,
42pxis substituted intobackground-color. - Because
42pxis not a valid color, the property becomes invalid at computed-value time. - The property switches to
unset. - Since
background-colordoes not inherit, it resets to itsinitialvalue:transparent. Thebluedeclaration is completely lost.
Limitations of the
var() Fallback
The standard fallback parameter in var()—such as
var(--accent-color, blue)—does not protect against IACVT
errors. The fallback inside var() only triggers if the
variable itself is completely undefined or registered as an
initial/guaranteed-invalid value. If the variable is defined, even with
an invalid token stream like 42px, the var()
function considers itself successfully resolved and does not trigger the
internal fallback.
How to Prevent IACVT Issues
To avoid unexpected resets to unset, use the CSS
Properties and Values API (@property). By explicitly
registering a custom property with a defined type syntax, you allow the
browser to validate the variable at parse time:
@property --accent-color {
syntax: '<color>';
inherits: false;
initial-value: black;
}When a custom property is registered with a specific syntax, setting
--accent-color: 42px; fails validation early, preserving
standard CSS cascade fallbacks and preventing unexpected property resets
across your layouts.