CSS Foundations: What is IACVT?

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.

~

# Table of Contents

~

# Invalid Declarations

In CSS all (non-custom) properties only accept values of a certain type. For example, the background-color property is defined to only accept a <color> type of value.

Name
background-color
Value
<color>
Initial
transparent
Applies to
all elements
Inherited
no
Percentages
N/A
Computed value
computed color
Canonical order
per grammar
Animation type
by computed value

The 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.

~

# CSS Value Processing

Between you declaring a value in your stylesheet and an element knowing which value to actually use, there’s a whole multi-step calculation that kicks into action. This process is known as CSS Value Processing.

  1. 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.
  2. Cascading yields the cascaded value. There is at most one cascaded value per property per element.
  3. Defaulting yields the specified value. Every element has exactly one specified value per property.
  4. Resolving value dependencies yields the computed value. Every element has exactly one computed value per property.
  5. 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.
  6. 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.

~

# Example: From 1.2em to 14px in 6 steps

For example, take this rule:

p {
	font-size: 16px;
	font-size: 1.2em;
}

The Value Processing process works like this:

Declared Value
There are two declared values here for font-size, both competing with each other.
Cascaded Value
After running the cascade, the winning declaration is font-size: 1.2em; so the cascaded value is 1.2em.
Specified Value
Because there is no defaulting at play the specified value is also 1.2em
Computed Value
Calculating 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 Value
Because there are no remaining calculations to be done, this 14.1px also becomes the used value.
Actual Value
Depending 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.

~

# Custom Properties

Custom Properties is CSS’s answer to having variables in the language. These are not just static tokens that you declare once and can never change, but these are actual properties that take part in the cascade. You can change their value on specific elements, have them respond to media queries, etc.

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 Value
var(--color)
Cascaded Value
var(--color)
Specified Value
var(--color)
Computed Value
greenrgb(0, 128, 0)
Used Value
rgb(0, 128, 0)
Actual Value
rgb(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).

~

# IACVT: Valid Declarations with Invalid Values

While a declaration in the form of 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 Value
var(--not-a-color)
Cascaded Value
var(--not-a-color)
Specified Value
var(--not-a-color)
Computed Value
16px
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 its var() functions, is invalid

☝️ For completeness, the guaranteed-invalid value you see there is initial. But that’s food for a different blog post …

~

# The Consequences of IACVT

When a declaration is considered IACVT is does not get thrown away nor does it fall back to other declarations as those have already been thrown away at Cascaded-Value time.

Instead, the invalid value gets replaced by a different value. What exactly happens depends on the property’s type.

  • If the property is a non-registered custom property or a registered custom property with universal syntax it gets replaced by the guaranteed-invalid value.
  • In all other cases the value becomes unset, whose outcome depends on whether the property is allowed to inherit or not.

    • If the property is allowed to inherit, it behaves as inherit, so it inherits the computed value from its parent.
    • If the property is not allowed to inherit, it behaves as 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:

  1. The Cascaded Value is var(--not-a-color). The background-color: red; declaration gets thrown away because it is of no use.
  2. The background-color: var(--not-a-color); gets marked as IACVT at Computed-Value time.
  3. var(--not-a-color) gets replaced by unset because it was IACVT
  4. unset behaves as initial because background-color does not inherit
  5. The computed value becomes transparent 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.

~

# Summary

CSS has a process in place called Value Processing to determine the actual value to apply to an element. In this process, invalid declarations like 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.

~

# Spread the word

To help spread the contents of this post, feel free to retweet the announcements made on social media:

~

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Join the Conversation

2 Comments

  1. Great article! Thanks for highlighting how css processing works for all of us.
    Just one clarification question:

    In chapter: “IACVT: Valid Declarations with Invalid Values” css variable “–not-a-color” has value of 20px, but later in css processing breakdown there is 16px, as computed value. How it came from 20 to 16 pixels?

    1. Good question! It falls back to 16px because that is what is declared in the User-Agent Stylesheet (the stylesheet that comes with the browser).

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.