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:

  1. Substitution Check: The browser evaluates the var() function and replaces it with the custom property's tokens.
  2. Validation: The resolved value is checked against the target CSS property's allowed syntax.
  3. Invalidation: If the syntax is incompatible (e.g., passing 16px to background-color), the declaration fails validation.
  4. Resolution via unset: The browser replaces the entire value with the unset keyword.

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 declared color: inherit;.
  • Non-Inherited Properties (e.g., background-color, margin, display): The element reverts to the CSS specification's default initial value. For instance, background-color reverts to transparent.

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:

  1. Both declarations are parsed. The second background-color wins the cascade because it is valid syntax at parse time.
  2. At computed-value time, 42px is substituted into background-color.
  3. Because 42px is not a valid color, the property becomes invalid at computed-value time.
  4. The property switches to unset.
  5. Since background-color does not inherit, it resets to its initial value: transparent. The blue declaration 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.