Lea Verou’s Dynamic CSS Secrets Takeaways

Ingvild Forseth
23 min readFeb 10, 2023

Again, this is a summary of a talk, made in order to 1) hopefully learn something and 2) have an easy access to the valuable information that is contained in a ~1h YouTube video.

Hopefully, this will help me and anyone in need of it, avoid endless scrubbing in a Youtube video, searching for that specific use case you are looking for a solution for.

All credit goes to Lea Verou, who is such an inspiration and such a cool lady. She is a W3C Tag (the technical architecture group that reviews proposals for future specs and writes documents of the underlying architectural principals of the web, belonging to the World Wide Web Consortium, which is an international community in which member organizations, a full time staff and the public work together to develop web standards, puh!) member. Her day job is however researching and teaching at MIT. She has her own blog too, and has written the book CSS Secrets: Better Solutions to Everyday Web Design Problems.

Disclaimer: The code examples in this text are freely edited and does not reflect the exact lines of Lea’s code. Also, the interpretation of each takeaway/tips are my own and should not be understood as Lea’s exact words.

This article is long and the timestamps underneath each takeaway is where in the presentation Lea has written the takeaway, but she explains each takeaway before the slide showing the takeaway.

Takeaway #1: CSS variables are just regular variables

(08:42)

The example Lea uses to underline this takeaway, is perhaps the most straightforward one, as it shows a pattern that most developers are already familiar with. Her point is that CSS variables are just like regular variables in the sense that they helps reduce lines of code and repetition. The pattern is used to implement style variation of a component.

Without using css custom properties, different style variations would typically be implemented as shown below, redeclaring CSS properties and giving them a different value for different variations ⬇️ : (the two following code blocks are freely edited for the purpose of simplification and do not reflect the exact lines of Lea’s code)

button {
border: .1em solid black;
color: black;
}
button.pink {
border: .1em solid pink;
color: pink;
}
button.green {
border: .1em solid green;
color: green;
}

By instead implementing it like this, utilising CSS custom properties, we avoid repeating CSS properties and thereby simplifying the code ⬇️ :

button {
--color: black;
border: .1em solid var(--color);
color: var(--color);
}
button.pink {
--color: pink;
}
button.green {
--color: green;
}

Takeaway #2: Pseudo-private variables succinctly reduce fallback duplication

(11:51)

Lea proposes to make use of the underscore syntax to introduce the idea of private CSS custom properties, something that has already been utilised in JavaScript and other programming languages for many years already, but that currently had no equivalent in CSS.

In the previous example, the code was error prone as it had no fallback values in case the --color CSS custom property did not exist. A fallback value could be implemented like this, in which a fallback value must be included every time the --color is used ⬇️ :

button {
--color: black;
border: .1em solid var(--color, black);
color: var(--color, black);
}
button.pink {
--color: pink;
}

Instead, the fallback value can be included once in a private CSS custom property called --_color ⬇️ , and thereby making the code more readable:

button {
--_color: var(--color, black);
border: .1em solid var(--_color);
color: var(--_color);
}
button.pink {
--color: pink;
}

Takeaway #3: CSS Variables allow us to create a higher level styling API

(13:05)

What Lea means by this, is that by utilising CSS custom properties, we are able to produce components that are more encapsulated in terms of styling than before. The CSS custom properties can be seen as a new styling API of a component that we can interact with. When utilised right, we can provide components that we can change the internal implementation of without breaking already written code.

Let’s say the design spec changes. Previously, the hover effect of a button was a background change ⬇️ :

button {
--_color: var(--color, black);
border: .1em solid var(--_color);
color: var(--_color);
}
button:hover {
background: var(--_color);
color: white;
}
button.pink {
--color: pink;
}

It should now instead get a box-shadow effect on hover. The only change would be to change the background declaration to box-shadow: 0 0 0 1em var(--_color) inset;, one line of code change ⬇️ :

button {
--_color: var(--color, black);
border: .1em solid var(--_color);
color: var(--_color);
}
button:hover {
box-shadow: 0 0 0 1em var(--_color) inset; /* Updated */
color: white;
}
button.pink {
--color: pink;
}

On the other hand, if the variations of the component was implemented as below, without the use of CSS custom properties, the implementation would break if one forgot to change the line of code not only one place, but multiple ones ⬇️ :

button {
border: .1em solid black;
color: black;
}
button:hover {
box-shadow: 0 0 0 1em black inset; /* Updated */
color: white;
}
button.pink {
border: .1em solid pink;
color: pink;
}
button.pink:hover {
background: pink; /* This place also need to be updated */
}

Takeaway #4: CSS variables cannot be interpolated unless we register them with @property

(16:29)

Using the following lines of code, the buttons color hue would just go straight from 0 to 180 degrees after 5 seconds ⬇️ :

@keyframes color-hue {
from { --color-hue: 0; }
to { --color-hue: 180; }
}
button.rainbow {
--color: hsl(var(--color-hue) 60% 50%)
animation 5s color-hue linear infinite;
}

… unless we include a @property registration for the --color-hue CSS custom property. This allows a CSS custom property to animate nicely throughout keyframes, in this case making the button’s --color property animate halfway through the color hue wheel in 5 seconds ⬇️ :

@property --color-hue {
syntax: "<number>";
initial-value: 0;
inherits: true;
}
@keyframes color-hue {
from { --color-hue: 0; }
to { --color-hue: 180; }
}
button.rainbow {
--color: hsl(var(--color-hue) 60% 50%)
animation 5s color-hue linear infinite;
}

One can also register a CSS custom property in JavaScript using CSS.registerProperty() . Lea mentions a few reason when this option can be used, and that is either a) when multiple similar CSS custom properties needs registration, or b) for debugging reasons, as there is little feedback using the @property syntax if something does not work, and the JavaScript API gives nice readable error messages.

Takeaway #5: @property-defined initial values take precedence over var() fallback

(19:20)

Lea mentions this as one has to be aware of that if one registers a custom property for instance due to the need of it being interpolatable, you automatically need to give it an initial value. All three descriptors syntax , initial-value and inherits are mandatory when registering a CSS custom property (only exception is that we can omit the initial-value descriptor if the syntax is a wild card).

If an initial-value for a CSS custom property is defined (meaning it as been registered using the @property), the entire use case for pseudo private variables to provide a fallback value efficiently is eliminated. A fallback value in var() will never apply, the initial-value always takes precedence.

However, the use case for pseudo private variables is not entirely eliminated. Registering a CSS custom property using the @property in your stylesheet will make the CSS custom property globally scoped. Sometimes that is not desirable. For instance, sometimes one can use fairly generic CSS custom property names like--color that should be locally scoped to a component, and in that use case, one might not want its value to fallback to some globally initial color. The only way to get a registration of a CSS custom property to be locally scoped, if its registration happens in the shadow tree, then it will be scoped to the shadow tree.

Thus, there are use cases for either, for registering a property globally, or rather giving it a fallback value with the var() syntax/using a pseudo private variable. There is one small caveat here though. If @property is not supported, the var fallback in var() will actually apply regardless.

Takeaway #6: Use variables for pure data, not CSS values

(38:12)

In CSS today, you can give a CSS custom property a unit by multiplying it using calc ⬇️ :

/* Number to unit */
height: calc(var(--foo) * 1%);
width: calc(var(--foo) * 1px);

However, there is no way in CSS today to possibly obtain the numeric value from a CSS custom property if the property is already defined with a unit. For this reason, Lea’s advice is to keep the values of CSS custom properties pure, and without unit. This is especially important if the value comes from JavaScript, because if a different unit is needed in CSS, you don’t need to go to your JavaScript to change units.

/* Unit to number, not possible! */
--foo: 42px; /* There is no way to get the value 42 from this var */

One small additional detail, in the future, also number to unit conversion should be possible by using calc with division: calc(var(--foo) / 1%) ;

Takeaway #7: content only accepts strings. You can convert numbers to strings via counter-reset

(39:54)

This takeaway combines with the previous one. The idea Lea wants to show in her example how should be done, is how to be able to output the value of a CSS custom property on the web page.

The example she uses is to get a bar chart to display the percentage of each column, with the percentage of each column given using a CSS custom property ⬇️ :

<div class="bar-chart">
<div style="--p: 26">Column 1</div>
<div style="--p: 100">Column 2</div>
...
</div>
This is the result Lea want to obtain in her demonstration on how to get the value of a CSS custom property displayed on the web page. (Source: Youtube video)

This will unfortunately not be possible with the following lines of code ⬇️ :

.bar-chart > div {
/* Number to unit conversion */
height: calc(var(--p) * 1%;
}
.bar-chart > div::before {
/* This will not work */
content: var(--p);
}

It does not work because the CSS property content only accepts strings. You can however convert number to strings using the CSS property counter-reset , making these lines of code work ⬇️ :

.bar-chart > div::before {
counter-reset: p var(--p);
content: counter(p) "%";
}

Takeaway #8: … but you can only convert integers

(40:37)

Again, following up the previous takeaway, the numbers that can be converted using counter-reset can only be intergers, thus, the bar-chart example would not work for the following column, it would just get output 0% ⬇️ :

<div class="bar-chart">
...
<div style="--p: 81.25">Column X</div>
...
</div>

Takeaway #9: Convert a number to an integer by assigning it to a property registered as “<integer>” inside calc()

(41:56)

But, of course Lea does not accept this defeat. We need to be able to convert the float number we are receiving to an integer. By utilising the @propertysyntax, and also wrapping the original number/CSS custom property in calc(), we can convert the number to an integer. Lea credits Ana Tudor for figuring out this integer casting trick in CSS ⬇️ :

@property --integer {
syntax: "<integer>";
initial-value: 0;
inherits: true;
}
....bar-chart > div::before {
--integer: calc(var(--p));
counter-reset: p var(--integer);
content: counter(p) "%";
}

And this is how you control the conversion:

/* Round: */
--integer: calc(var(--number));
/* Floor: */
--integer: calc(var(--number) - 0.5);
/* Ceil: */
--integer: calc(var(--number) + 0.5);

For reference, it is actually planned for the future of CSS, that it will provide rounding functions. And she also claims that for the even further future of CSS, we won’t hopefully not even need the counter-reset trick for text conversion of CSS custom properties, we will even get a text() function that will convert any value to a string️, the console.log() of CSS she compares it to ⬇️ :

/* Future CSS */
/* Round: */
counter-reset: p round(var(--p), 1);
/* Floor: */
counter-reset: p round(down, var(--p), 1);
/* Ceil: */
counter-reset: p round(up, var(--p), 1);
/* Even further future CSS */
content: text(var(--p)) "%";

Takeaway #10: CSS variable conditionals

(45:49)

Continuing on the same example with the bar chart, it is also desirable to make the color of each column of the bar-chart depend on the percentage it represents. Deciding on a range, it means that we want the color of a close to 0% column be yellow, but the column of a close to a 100% column be blue, making the columns interpolate the hue of the color wheel, and also slightly change the lightness of the column.

This is a special case of CSS variable conditionals that is called a linear mapping — we want to map a number into a representative number in an other number space (i.e. x ∈ [x1, x2] → y ∈ [y1,y2], to remind myself about the happy times, being able to use mathematical notation when in university 😍). Lea even made a webpage to not having to remember the formula for achieving this every time, css.land/ranges. The formula has to be implemented something like this in CSS ⬇️ :

--y: calc((((var(--x) - x1) * (y2 - y1)) / (x2 - x1)) + y1);

Takeaway #11: Linear mapping allows the same variable to control multiple things, continuously or discretely

(47:12)

Lea continues on her topic on linear mappings, by showing us how the linear mapping function gets even more simplified when used in a discrete setting, more precisely to create conditionals, meaning when one simply want one value of a CSS property, or another. We want to control this with a CSS custom property that either has a value of 0 (off), or a value of 1 (on), basically a ternary expression. When the range does not matter, the formula gets simplified to what is shown in the code block below. However, since the calc() function is still required, it is important to remember that this trick for creating conditionals only applies as a solution for setting values for CSS properties that take on numerical values, like line-height️ for example ⬇️ :

--is-true: 1;
--is-not-true: calc(1 - var(--is-true);
-property: calc(var(--is-true) * value_if_true + var(--is-not-true) * value_if_false);// Example for how to change the line-height for a glossy button:
line-height: calc(var(--glossy) * 1.1 + var(--not-glossy) * 1.5);

Logical operators

The trick for creating conditionals for values of numerical CSS properties above, utilises how CSS custom properties can be used to create logical operators. In the example above, the not operator was utilised. Lea shows how boolean algebra can be applied in CSS to create two additional logical operators, the or and and operator⬇️ :

--not-a: calc(1 - var(--a));
--a-or-b: min(var(--a) + var(--b), 1);
--a-and-b: calc(var(--a) * var(--b));

Space Toggles

(The subject of space toggle is explained in the video from around 20:05.)

Earlier in her talk, Lea presented the concept of space toggles, as a mean for creating conditionals. The way of using linear mappings to achieve this is a simplified, more readable solution she claims. However, linear mappings to create conditionals are limited to CSS properties that take on numerical values only (as previously stated), whereas space toggles are not. Space toggles can be used for creating conditionals for the background or box-shadow property too for instance. It is a more powerful tool, although it perhaps provides a less readable solution for creating conditionals for numerical values in CSS, it is still included in this article, as it is a tool that makes us able to solve a bigger set of the problems than the linear mapping function for creating conditionals allows us to.

Lea starts off her explanation of the concept space toggles by giving her thoughts about CSS variables as an API. In that context, she brings up the fact that the W3C in their guidelines for creating web platform compatible components actually advises against using custom attributes for styling. The reason why developers do it regardless, is that there is yet a way to achieve conditionals in CSS.

Don’t use custom attributes for styling, like bgcolor. Instead use Custom Properties or CSS Shadow Parts. — in 1.1.4 Guidelines for creating web platform compatible components by W3C

To give an example, this is for instance how the component library Shoelace utilises what Lea claims is an anti-pattern to achieve an outlined button, by introducing a custom attribute tag outline ⬇️ :

<sl-button variant="default" outline>Default</sl-button>

In the future, CSS will provide style container queries, which are described in greater detail by Una Kravitz in her article Style Queries, and it will look something like this ⬇️ :

/* Future CSS */
@container style(--glossy:on) {
button {
box-shadow: 0 .1em hsl(0 0% 100% / .4) inset;
}
}

However, since the future has yet to come, Lea demonstrates how we can fake this behaviour with what she calls space toggles. It looks something like this ⬇️ :

:root {
--ON: initial;
--OFF: ; /* value is a white space */
button:nth-child(even) {
--glossy: var(--ON);
}
button {
background: var(--glossy, linear-gradient(hsl(0 0% 100% / .4), transparent) pink;
box-shadow: var(--glossy, 0 .1em hsl(0 0% 100% / .4) inset);
border: var(--glossy, .05em solid pink);
}

To explain what is happening her, this idea was born after the realization that white space is in fact a perfectly valid property value: --foo: ; . So to turn something off, we make a var() statement that will just be evaluated as a white space, and then providing the value we want when opting out (in this case just a plain background, pink, and no linear-gradient) after the var() statement, like: var(--foo, linear-gradient(..)) pink. This works since white-spaces are stripped away during evaluation time in CSS, leaving it to become: background: pink;

To turn something on, the initial value is used instead of a whitespace, as the initial value works as if the CSS custom property was never given a value, making the fallback in the var statement kick in. This is what is described in the following code block ⬇️ :

/* White space is a valid value, so the fallback does not kick in */
.off {
--foo: ; /* Value is a white space */
background: var(--foo, linear-gradient(white, transparent)) pink;
}
/* initial triggers the fallback, as if the variable had no value */
.on {
--foo: initial;
background: var(--foo, linear-gradient(white, transparent)) pink;
}

Some more details of this solution needs to be pointed out in order to understand the full picture of how this works. One of those points, is the concept of Invalid At Computed Value Time (IACVT) needs to be explained (explained in the video from around 26:17). It had to be invented to make CSS custom properties possible, Lea tells. IACVT happens when you provide a CSS property with a value that is not valid. Usually, the browser throws away lines of code it does not understand, but because all other possibly given fallbacks are thrown away by the browser ahead of the time of computation where the value is discovered to be invalid, the value is instead substituted to the value unset . Unset sets inherited properties to the inherit keyword, and non-inherited properties get the initial keyword (the closest thing we have to a reset keyword in CSS, Lea says) ⬇️ :

--foo: 42deg;
/* This declaration is invalid at computed value time (IACVT) */
background: var(--foo); /* It is treated as background: unset; */

This is the case for the border of the button in this example. Without declaring the border at all, the button would get the default border the browser provides. However, since the declaration border: var(--glossy, .05em solid pink); is evaluated as IACVT when --glossy has the value of a white space, the border is treated as border: initial; , and the initial value sets the border-style to none which means that the border is removed.

Also the line-height value of this example is a bit complex, because what we what in this example is a conditional. If not glossy, we want a line-height of 1.5, but if glossy, it should be a value of 1.1. This is achieved by using the space toggle in a calc() to trigger computation. When the space toggle is a white space, only calc(1.5) will remain in the expression, but if the space toggle has the value initial , the fallback will trigger, resulting in: calc(1.5 — .4)️ ⬇️ :

/* Space toggle version for numerical values */
line-height: calc(1.5 var(--glossy, - .4));
/* Fictional (future?) syntax, a ternary expression */
line-height: if(--glossy = initial, 1.1, 1.5);
// However, simplified version, using linear mapping for conditionals
line-height: calc(var(--glossy) * 1.1 + var(--not-glossy) * 1.5);

Lastly, it should not be expected that web developer that does not enjoy getting CSS-deep should remember that the white space value means off, and that the initial value means on. To abstract this detail away and create a nice API to work with, we introduce the --ON and --OFF CSS custom properties on :root that will have the values initial and ; (white space), respectively ⬇️ :

:root {
--ON: initial;
--OFF: ; /* white space is a valid value */
}

Space toggles can also be used to provide progressive enhancement in browsers that support a feature that others don’t, for instance different color spaces. This is explained in detail in the video from around 33.49, and left out of this article as it is just a further exploitation of the concept that has already been explained.

Takeaway #12: Range mapping allows you to decouple input from output

(49:27)

For this takeaway, Lea uses a svg illustrated in the image underneath ⬇️, in which the location of the eyes are supposed to be controlled by the location of the user’s mouse pointer.

Lea produces an example in which the location of the eyes in an svg is controlled by the user’s mouse pointer. In the top image the mouse is to the left of the svg, in the bottom image, it is centered on screen.

However, they are controlled by a horizontal center, the cx css property, which gives the desired position of the eyes between 25-75-px. However, Lea desires to improve this API of the eye positioning, by making the input parameter for positioning more intuitive, and in addition make this input value less dependent on the CSS, so it would not have to change if other CSS properties changed value.

She introduces a CSS custom property--look that she want to control the eye positioning with, and by letting it’s range [0,1] map to the pixel range of the eye positioning, she decouples the input from the output values:

.iris {
--look: .5;
cx: calc(25px + 50px * var(--look));
}

Takeaway #13: Relative values inherit as syntax token unless the property is registered

(51:57)

To illustrate this takeaway, Lea creates something like this online — a little emoji with a speech bubble:

The code snippet below creates this element.

The code snippet below shows the most important code for creating this speech bubble example that gets her point across. The “pointer” refers to the little triangle above the emoji’s head. She shows us that without registering the css custom property, the following bug occurs when trying to resize the emoji:

  • Without registering the css custom property --pointer-size , when changing the emoji-sizeto e.g. 3em, the font-size of the after pseudo-element becomes 3em, and since the font-size property defines the size of an em in an element, and the margin-top that is set to 1em becomes 3 times as large as it originally was.
  • However, if registrering the property--pointer-height as showed in the example below as a length, what is inherited in the after pseudo-element is no longer what is called the syntax token 1em. The browser instead interprets it as length, which ensures that it is the actual length that is inherited, the pixel value as it is calculated in the .speech-bubble element where it is defined.
/*
Registring the css custom property makes the inherited value become
the calculated one.
In this case, it would ensure --pointer-height resolving to for example
16px, and not 1em.
*/
@property --pointer-height {
syntax: '<length>'
}

.speech-bubble {
...css declarations
--pointer-width: 1em;
--pointer-height: 1em;
--pointer-offset: 1.5em;
--emoji: '😄';
--emoji-size: 2em
}

/* The pointer: */
.speech-bubble::before {
width: var(--pointer-width);
height: var(--pointer-height);
right: var(--pointer-offset);
}

/* The emoji: */
.speech-bubble::after {
content: var(--emoji);
margin-top: var(--pointer-height);
font-size: var(--size);
}

Takeaway #14: Registering a property as <length> allows us to pass down ancestor font sizes

(52:03)

Another use case of takeaway #13, we can use this behaviour to pass down ancestor font sizes. Lea shows this using a html example with a group of options in which one can be selected, using the<select> and <optgroup> tags:

<select size="5">
<option value="">None</option>
<optgroup>
<option value="option1">Option 1</option>
... more options
</optgroup>
</select>

This HTML produces something like the leftmost lift below, a great gap between the selected value and the options. Lea want to improve the look of this <select> , by minimising this gap as showed in the rightmost image. The middle image shows how the select may up end looking when using the only solution (it is more of an hack) she was able to come up with before @property syntax was a reality.

Leftmost image: How a <section> with an <optgroup> and <option> tags look like natively in the browser. Middle image: How it may end up looking when trying to minimise the gap between the selected option and the options that can be selected without using the @property syntax. Rightmost image: Lea is able to minimise the gap and use the same font-size for the selected value and the options when using a css custom property that she registers using the @property syntax.

Before the @property syntax was supported in CSS, the only solution she was able to find, was to set the font-size value of the <optgroup> to zero, then setting the font-size of all its children. But, then the font-size may end up looking differently betweent the selected option and the options, as shown in the middle image above.

/* The ugly hack: */
optgroup {
font-size: 0
}

opt-group > * {
/*
Ideally, we want to set it back to the parent font-size,
but we don't have any reference to it, so making our best guess.
*/
font-size: 1rem;
}

If we instead save the select tag’s font-size value (which resolves to 1em in every element) in a css custom property, we are able to produce the look in the rightmost image above, in which the selected option and the options have the same font-size :

/* Instead passing down ancestor font-sizes: */
/*
NB: Without registering --parent-em, --parent-em would resolve to
it's direct parent's font-size, which is 0.
*/
@property --parent-em {
syntax: "<length>";
initial-value: 0;
inherits: true;
}

select {
---parent-em: 1em;
}

optgroup {
font-size: 0
}

opt-group > * {
font-size: var(--parent-em);
}

What is important when using this trick, is that in the case of lacking browser support for the @property syntax (which usually only results in a slightly less refined result on the web page), the options would would actually no longer show, since the inherited font-size becomes zero. This is because the support for @property is actually much less than for CSS custom properties, which can be seen in the image below. Still to this day (Jan 2023), Safari and Firefox which at the time of the video had a marked share of ~30%, have still yet to implement support for the @property syntax:

Showing that the browser support for @property and CSS.registerProperty() still lacks in the Safari browser and the Firefox browser. (Source: YouTube video)

For this reason, it is very important in this case to implement a correct fallback. This can be done by detecting @property support using the following code snippet adding a class to root, and then add it as a descendent where ever needed:

if(window.CSSPropertyRule) {
let root = document.documentElement;
root.classList.add("supports-at-property")
}

/* How to use it in CSS: */
.supports-at-property optgroup {
font-size: 0;
}

However, would it not be great if we instead could just detect @property syntax in CSS alone? Unfortunately, the @supports syntax can not be used directly as outlined in the code block below:

/* 
Leveraging this syntax would be great to detect @property support,
but it is unfortuneately not possible.
*/
@supports (???) {
optgroup {
font-size: 0;
}
}

Lea’s first inclination was that perhaps it would be possible to detect the invalidity of a css custom property. By first registering it, and then assigning something invalid to it, she hoped one would be able to produce a fallback, here illustrated by registering a color property, then giving it the value of 0:

/* Failed attempt for detecting lacking @property support in CSS: */
@property --color-test {
syntax: "<color>";
initial-value: transparent;
inherits: true;
}

@supports {--color-test:0) {
/* Does not support @property */
:root {
background:red;
}
}

The reason the code block above fails as a mechanism for detecting lacking @property support in CSS in a browser, is:

“The syntax of registered properties is only checked at computed value time” — Lea Verou (YouTube, 55:27)

This means it is not checked at parsed time. This makes sense because for traditional CSS properties, the value would not change, but for css custom properties, the value of it can change any time. Instead, we can leverage inheritance to detect @property support in CSS, by registering a CSS custom property (in this case--no-at-property-fallback ) with inherits: false , set it to a value at an ancestor and combine it with a fallback as shown ⬇️. In the example below, the background only becomes green if the @property syntax is not supported.

/* 
Leveraging that Safari and Firefox browsers still support CSS custom
properties, but not the @property.
This makes the --no-at-property-fallback property only inherited in those
browsers, which can be used to produce a fallback (red background color).
*/
@property --no-at-property-fallback {
syntax: "*";
inherits: false;
}

:root {
/* Will only inherit if @property is ignored */
--no-at-property-fallback: red;
}

body {
background: var(--no-at-property-fallback, green)
}

In the exact use case of inheriting a parent font-size , it would look like this (not tested, and Lea did not show this in her talk, but I think it would look like this 🙃):

@property --parent-em {
syntax: "<length>";
initial-value: 0;
inherits: true;
}

@property --no-at-property-fallback {
syntax: "*";
inherits: false;
}

select {
---parent-em: 1em;
}

optgroup {
--no-at-property-fallback: 1rem;
font-size: 0:
}

opt-group > * {
font-size: var(--no-at-property-fallback, var(--parent-em));
}

Summary

Lea ends her talks with asking if these tricks she just showed are just hacks that should be avoided. Her reasoning around that subject, is that she thinks one should always prioritize API simplicity over implementation simplicity. The reason? The internal implementation can always be rewritten when native CSS support is available for that functionality, and also, the API is used a lot more and is thus more difficult to change when the implementation is done. She claims that the hacks she showed in this talk when precisely in the implementation.

You got to do what you got to do to expose a nice API — Lea Verou

Here are the takeaways summarized:

  1. CSS variables are just regular variables
  2. Pseudo-private variables succinctly reduce fallback duplication
  3. CSS Variables allow us to create a higher level styling API
  4. CSS variables cannot be interpolated unless we register them with @property
  5. @property-defined initial values take precedence over var() fallback
  6. Use variables for pure data, not CSS values
  7. Content only accepts strings. You can convert numbers to strings via counter-reset
  8. … but you can only convert integers
  9. Convert a number to an integer by assigning it to a property registered as “<integer>” inside calc()
  10. CSS variable conditionals
  11. Linear mapping allows the same variable to control multiple things, continuously or discretely
  12. Range mapping allows you to decouple input from output
  13. Relative values inherit as syntax token unless the property is registered
  14. Registering a property as <length> allows us to pass down ancestor font sizes

Lea’s motivation for the talk

I know this topic may occur a bit out of order, but I wanted to skip right to the good stuff/what one usually is searching for in a YouTube video in this article. However, I wanted to include this bit too, as I found it really interesting. It teaches one a bit how the development of the web is looked after.

Lea starts of her talk with explaining the motivation behind it. In 2020, she was the lead of the css chapter for the Web Almanac, which is the HTTP Archive’s annual state of the web report. It reveals how developers currently are utilising the available web technologies.

Lea was surprised to see that developers were currently still using css custom properties first and foremost in a basic way, on a surface level, still not taking advantage of it in higher potential use cases, even though the css custom properties has been available to developers more than 5 years ago (from around 2016). This can be explained by introducing the term variable depth, which is how many variable references do you have to look up in order to obtain the current value.

In 2020, more than 60% of the css custom properties uses in 2020 was on variable depth zero, which means just declaring the css custom property as shown below ⬇️:

... {
/* variable depth zero */
--hue: 360;
}

Also, most css custom properties were declared on the :root level, which essentially means utilised as just constants:

Most css custom properties were declared on the :root level in 2020. (Source: YouTube video)

Lea’s thoughts on this, is that we have this awesome opportunities to use css custom properties more as reactive properties, and not just as constants (andthe reason for why this is the main perception, is that it was evangelised that you with css custom properties you finally have reusable colors and fonts). In her talk, she shows us how to use css custom properties as reactive properties.

--

--

Ingvild Forseth

I am a software developer who also likes to write. This platform is mostly used to document my process of learning. spinnvild.web.app.