I’m running a poll on socials (Twitter, Mastodon) asking whether DevTools should flag the IACVT state of a declaration or not.
Besides the obvious “Yes” and “No”, I also added a “What’s IACVT?” option.
To my surprise, many respondents chose that last answer I provided … so here’s a post that tells you all about “Invalid At Computed Value Time” or IACVT for short.
~
1.2em to 14px in 6 steps~
background-color property is defined to only accept a <color> type of value.Namebackground-colorValue<color>InitialtransparentApplies toall elementsInheritednoPercentagesN/AComputed valuecomputed colorCanonical orderper grammarAnimation typeby computed valueThe definition also answers questions such as “what is the initial value”, “does this property inherit”, “how does it behave when used in an animation”, etc.
If you try to put something like 20px – which is a <length> – into the background-color property, the declaration gets discarded because it has an invalid property value.
body {color: blue;~~color: 20px;~~ /* ❌ Invalid Property Value */}
These invalid declarations get discarded very early in the CSS Value Processing process. When collecting the Declared Values, User-Agents (= spec lingo for browsers) already filter out syntactically invalid declarations:
In order to find the declared values, implementations must first identify all declarations that apply to each element. A declaration applies to an element if:
- It belongs to a style sheet that currently applies to this document.
- It is not qualified by a conditional rule with a false condition.
- It belongs to a style rule whose selector matches the element. (Taking scoping into account, if necessary.)
- It is syntactically valid: the declaration’s property is a known property name, and the declaration’s value matches the syntax for that property.
In case you were wondering: the <color> type itself is split up into several sub-types, and therefore accepts a multitude of possible values. It is defined to accept hex colors, named colors, the currentcolor keyword, color functions (such as rgb() and oklab()), light-dark(), etc.
~
- First, all the declared values applied to an element are collected, for each property on each element. There may be zero or many declared values applied to the element.
- Cascading yields the cascaded value. There is at most one cascaded value per property per element.
- Defaulting yields the specified value. Every element has exactly one specified value per property.
- Resolving value dependencies yields the computed value. Every element has exactly one computed value per property.
- Formatting the document yields the used value. An element only has a used value for a given property if that property applies to the element.
- Finally, the used value is transformed to the actual value based on constraints of the display environment. As with the used value, there may or may not be an actual value for a given property on an element.
~
1.2em to 14px in 6 stepsFor example, take this rule:p {font-size: 16px;font-size: 1.2em;}
The Value Processing process works like this:
Declared ValueThere are two declared values here for font-size, both competing with each other.Cascaded ValueAfter running the cascade, the winning declaration is font-size: 1.2em; so the cascaded value is 1.2em.Specified ValueBecause there is no defaulting at play the specified value is also 1.2emComputed ValueCalculating the computed value resolves the specified value as far as possible without laying out the document or performing other expensive operations. Generally speaking, computing a relative value absolutizes it so the 1.2em computes to 14.1px (for example).Used ValueBecause there are no remaining calculations to be done, this 14.1px also becomes the used value.Actual ValueDepending on the user agent / hardware – e.g. on a device that can only draw full pixels – this 14.1px might become 14px which is the actual value.👨🏫 The spec has more examples of how certain values get processed.
~
To use them in a declaration, use the var() function.
:root { --color: green; }p { background-color: var(--color); }
These var() functions are substituted at computed-value time.
Declared Valuevar(--color)Cascaded Valuevar(--color)Specified Valuevar(--color)Computed Valuegreen → rgb(0, 128, 0)Used Valuergb(0, 128, 0)Actual Valuergb(0, 128, 0)In this case here, you end up with a green background-color, as you might have expected 🙂
Note that green resolves to rgb(0, 128, 0) as per css-color-4 spec:
If the sRGB color was explicitly specified by the author as a named color […] the computed and used value is the corresponding sRGB color, paired with the specified alpha channel (after clamping to [0, 1]) and defaulting to opaque if unspecified).
~
property: var(--varname) is valid in itself, you can end up with a declaration that makes no sense when the var() function gets substituted.Consider the following example from the spec:
:root { --not-a-color: 20px; }p { background-color: red; }p { background-color: var(--not-a-color); }
When processing the values, you end up with this:
Declared Valuevar(--not-a-color)Cascaded Valuevar(--not-a-color)Specified Valuevar(--not-a-color)Computed Value16px ❌Used Value??Actual Value??The 16px you see at computed-value time there is problematic, because 16px is not a valid value for background-color which only accepts <color>s.
This declaration is therefore considered Invalid at Computed-Value Time, or IACVT for short:
A declaration can be invalid at computed-value time if it contains a
var()that references a custom property with the guaranteed-invalid value […] or if it uses a valid custom property, but the property value, after substituting itsvar()functions, is invalid
☝️ For completeness, the guaranteed-invalid value you see there is initial. But that’s food for a different blog post …
~
Instead, the invalid value gets replaced by a different value. What exactly happens depends on the property’s type.
unset, whose outcome depends on whether the property is allowed to inherit or not.inherit, so it inherits the computed value from its parent.initial, falling back to the initial value.So winging back to this example:
:root { --not-a-color: 20px; }p { background-color: red; }p { background-color: var(--not-a-color); }
Things play out like this:
var(--not-a-color). The background-color: red; declaration gets thrown away because it is of no use.background-color: var(--not-a-color); gets marked as IACVT at Computed-Value time.var(--not-a-color) gets replaced by unset because it was IACVTunset behaves as initial because background-color does not inherittransparent as that is the initial value for background-color.Here’s a demo that shows this. The background is not red but transparent.
See the Pen CSS IACVT Example by Bramus (@bramus) on CodePen.
💡 If you would have authored background-color: 20px directly (i.e. without the use of Custom Properties), then that declaration would have simply been discarded due to being invalid, and you would have ended up with a red background.
~
color: 20px get filtered out upfront.Declarations with custom properties like color: var(--not-a-color) can initially make it through, but can get kicked out in case the substituted value does not match the type the property expects.
Because this invalidation happens at Computed-Value time, the declaration is flagged as “Invalid at Computed-Value Time” (IACVT).
When IACVT occurs, the invalid value gets replaced by either the guaranteed-invalid value or unset, depending on what property is targeted.
~
For those who answered “What’s IACVT?” I wrote a blog post for you: https://t.co/vbMkPxExPJ
— Bramus (@bramus) February 26, 2024
~
🔥 Like what you see? Want to stay in the loop? Here's how: