Reading List
The most recent articles from a list of feeds I subscribe to.
Inherit ancestor font-size, for fun and profit
If you’ve been writing CSS for any length of time, you’re probably familiar with the em unit, and possibly the other type-relative units. We are going to refer to em for the rest of this post, but anything described works for all type-relative units.
As you well know, em resolves to the current font size on all properties except font-size, where it resolves to the parent font size. It can be quite useful for making scalable components that adapt to their context size.
However, I have often come across cases where you actually need to “circumvent” one level of this. Either you need to set font-size to the grandparent font size instead of the parent one, or you need to set other properties to the parent font size, not the current one.
If you’re already familiar with the problem and just want the solution, skip ahead. The next few paragraphs are for those thinking “but when would you ever need this?”
Sometimes, there are workarounds, and it’s just a matter of keeping DRY. For example, take a look at this speech bubble:
Note this in the CSS:
/* This needs to change every time the font-size changes: */
top: calc(100% + 1em / 2.5);
font-size: 250%;
Note that every time we change the font size we also need to adjust top. And ok, when they’re both defined in the same rule we can just delegate this to a variable:
--m: 2.5;
top: calc(100% + 1em / var(--m));
font-size: calc(var(--m) * 100%);
However, in the general case the font size may be defined elsewhere. For example, a third party author may want to override the emoji size, they shouldn’t also need to override anything else, our CSS should just adapt.
In other cases, it is simply not possible to multiply and divide by a factor and restore the ancestor font size. Most notably, when the current (or parent) font-size is set to 0 and we need to recover what it was one level up.
I’ve come across many instances of this in the 16 years I’ve been writing CSS. Admittedly, there were way more use cases pre-Flexbox and friends, but it’s still useful, as we will see. In fact, it was the latest one that prompted this post.
I needed to wrap <option> elements by a generic container for a library I’m working on. Let me stop you there, no, I could not just set classes on the options, I needed an actual container in the DOM.
As you can see in this pen, neither <div> nor custom elements work here: when included in the markup they are just discarded by the parser, and when inserted via script they are in the DOM, but the options they contain are not visible. The only elements that work inside a <select> are: <option>, <optgroup>, and script-supporting elements (currently <template> and <script>). Except <optgroup>, none of the rest renders any contents and thus, is not fit for my use case. It had to be <optgroup>, sadly.
However, using <optgroup>, even without a label attribute inserts an ugly gap in the select menu, where the label would have gone (pen):

(There were also gaps on the left of the labels, but we applied some CSS to remove them)
There appears to be no way to remove said gap.
Ideally, this should be fixed on the user agent level: Browsers should not generate a label box when there is no label attribute. However, I needed a solution now, not in the far future. There was no pseudo-element for targeting the generated label. The only solution that worked was along these lines ([pen](optgroup:not([label]) { display: contents; font-size: 0; }
optgroup:not([label])> * { font-size: 13.333px; })):optgroup:not([label]) {
font-size: 0;
}
optgroup:not([label]) > * {
font-size: 13.333px;
}
The weird 13.333px value was taken directly from the Chrome UA stylesheet (as inspected). However, it is obviously flimsy, and will break any additional author styling. It would be far better if we could say “give me whatever 1em is on the grandparent”. Can we?
The solution
What if we could use custom properties to solve this? Our first attempt might look something like this:
select {
--em: 1em;
}
optgroup:not([label]) {
font-size: 0;
}
optgroup:not([label]) > * {
font-size: var(--em);
}
However this is horribly broken:

All the options have disappeared!!
What on Earth happened here?!
By default, custom properties are just containers for CSS tokens.When they inherit, they inherit as specified, with only any var() references substituted and no other processing. This means that the 1em we specified inherits as the 1em token, not as whatever absolute length it happens to resolve to on select. It only becomes an absolute length at the point of usage, and this is whatever 1em would be there, i.e. 0. So all our options disappeared because we set their font size to 0!
If only we could make 1em resolve to an actual absolute length at the point of declaration and inherit as that, just like native properties that accept lengths?
Well, you’re in luck, because today we can!
You may be familiar with the @property rule as “the thing that allows us to animate custom properties”. However, it is useful for so much more than that.
If we register our custom property as a <length>, this makes the 1em resolve on the element we specified it on, and inherit as an absolute length! Let’s try this:
@property --em {
syntax: "<length>";
initial-value: 0;
inherits: true;
}
select {
--em: 1em;
}
optgroup:not([label]) {
display: contents;
font-size: 0;
}
optgroup:not([label]) > * {
font-size: var(--em);
}
/* Remove Chrome gap */
:where(optgroup:not([label]) > option)::before {
content: "";
}
And here is the same technique used for the speech bubble:
Fallback
This is all fine and dandy for the 68% (as of June 2021) of users that are using a browser that supports @property, but what happens in the remaining 32%? It’s not pretty:

We get the default behavior of an unregistered property, and thus none of our options show up! This is bad.
We should clearly either provide a fallback or conditionally apply these rules only in browsers that support @property.
We can easily detect @property support in JS and add a class to our root element:
if (window.CSSPropertyRule) {
let root = document.documentElement;
root.classList.add("supports-atproperty");
}
Then we can just use the descendant combinator:
:root.supports-atproperty optgroup:not([label]) {
font-size: 0;
}
CSS-only fallback for @property
While the JS fallback works great, I couldn’t help but wonder if there’s a CSS only way.
My first thought was to use @supports:
@supports (--em: flugelhorn) {
/* Does not support @property */
}
The theory was, if a browser supported any value to be assigned on a property registered as a <length>, surely it does not support property registration.
It turns out, registered properties do not validate their syntax at parse time, and thus are always valid for @supports. This is explained in the spec:
When parsing a page’s CSS, UAs commonly make a number of optimizations to help with both speed and memory.
One of those optimizations is that they only store the properties that will actually have an effect; they throw away invalid properties, and if you write the same property multiple times in a single declaration block, all but the last valid one will be thrown away. (This is an important part of CSS’s error-recovery and forward-compatibility behavior.)
This works fine if the syntax of a property never changes over the lifetime of a page. If a custom property is registered, however, it can change its syntax, so that a property that was previously invalid suddenly becomes valid.
The only ways to handle this are to either store every declaration, even those that were initially invalid (increasing the memory cost of pages), or to re-parse the entire page’s CSS with the new syntax rules (increasing the processing cost of registering a custom property). Neither of these are very desirable.
Further, UA-defined properties have their syntax determined by the version of the UA the user is viewing the page with; this is out of the page author’s control, which is the entire reason for CSS’s error-recovery behavior and the practice of writing multiple declarations for varying levels of support. A custom property, on the other hand, has its syntax controlled by the page author, according to whatever stylesheet or script they’ve included in the page; there’s no unpredictability to be managed. Throwing away syntax-violating custom properties would thus only be, at best, a convenience for the page author, not a necessity like for UA-defined properties.
Ok this is great, and totally makes sense, but what can we do? How can we provide a fallback?
It turns out that there is a way, but brace yourself, as it’s quite hacky. I’m only going to describe it for entertainment purposes, but I think for real usage, the JS way is far more straightforward, and it’s the one I’ll be using myself.
The main idea is to take advantage of the var() fallback argument of a second registered variable, that is registered as non-inheriting. We set it to the fallback value on an ancestor. If @property is supported, then this property will not be defined on the element of interest, since it does not inherit. Any other properties referencing it will be invalid at computed value time, and thus any var() fallbacks will apply. If @property is not supported, the property will inherit as normal and thus using it becomes our fallback.
Here is an example with a simple green/red test to illustrate this concept:
@property --test {
syntax: "*";
inherits: false;
}
html {
--test: red;
}
body {
background: var(--test, green);
}
And here is how we can use the same concept to provide a fallback for the <select> example:
@property --test {
syntax: "*";
inherits: false;
}
select {
--test: 1em; /* fallback */
--em: 1em;
}
optgroup:not([label]) {
font-size: var(--test, 0);
}
Here is the finished demo.
Is the current tab active?
Is the current tab active?
Is the current tab active?
Today I ran into an interesting problem. Interesting because it’s one of those very straightforward, deceptively simple questions, that after a fair amount of digging, does not appear to have a definite answer (though I would love to be wrong!).
The problem was to determine if the current tab is active. Yes, as simple as that.
Why? (i.e. my use case)
I was working on my slide deck framework, Inspire.js. There is a presenter mode plugin, which spawns a new window with your slides (“projector view”), whereas your current window becomes a “presenter view”, with open notes, preview of the next slide, optional progress indicator for time etc.
However, this plugin was not very good. The two windows are synced, but only if you use presenter view to navigate slides. If you use the projector view to advance slides, the syncing breaks. Why would you use the projector mode? Many reasons, e.g. to interact with a live demo, or even play a video. If you have a live demo heavy presentation, you may even want to mirror your screen and only ever interact with the projector mode, while having the presenter mode on a secondary screen, just to look at.
The way the plugin worked was that every time the slide changed in the presenter view, it propagated the change in the projector view. To make the syncing bidirectional, it would be good to know if the current window is the active tab, and if so, propagate all slide navigation to the other one, regardless of which one is the projector view and which one is the presenter view.
And this, my friends, is how I ended up in this rabbit hole.
(Yes, there are other solutions to this particular problem. I could just always propagate regardless and have checks in place to avoid infinite loops. But that’s beside the point.)
What about the Visibility API?
In most resources around the Web, people were rejoicing about how the Visibility API makes this problem trivial. “Just use document.hidden!” people would gleefully recommend to others.
Yes, the Visibility API is great, when you want to determine whether the current tab is visible. That is not the same as whether it is active.
You may have two windows side by side, both visible, but only one of them is active. You may even have a window entirely obscuring another window, but you can still tab through to it and make it active. Active and visible are entirely orthogonal states, which are only loosely correlated.
In my use case, given that both the projector view and presenter view would be visible at all times, this is a no-go that doesn’t even solve a subset of use cases.
What about focus and blur events on window?
The other solution that was heavily recommended was using the focus and blur events on window. This does get us partway there. Indeed, when the current tab becomes active, the focus event fires. When another tab becomes active, the blur event fires.
Notice the emphasis on “becomes”. Events notify us about a state change, but they are no help for determining the current state. If we get a focus or blur event, we know whether our tab is active or not, but if we don’t get any, we simply don’t know. A tab can start off as active or not, and there is no way to tell.
How can a tab possibly start off as inactive? One easy way to reproduce this is to hit Return on the address bar and immediately switch to another window. The tab you just loaded just starts off as inactive and no blur event is ever fired.
What about document.activeElement?
The document.activeElement property will always return the currently focused element in a page. Can we use it to determine if a window currently has focus? Nope, cause that would be too easy.
Run setTimeout(() => console.log(document.activeElement), 2000) in the console and quickly switch windows. Return >2 seconds later and see what was logged. It’s the <body> element!
Wait, maybe we can assume that if the currently focused element is a <body> element then the current window is inactive? Nope, you get the same result in an active tab, if you simply haven’t focused anywhere.
What about document.hasFocus()?
When I discovered document.hasFocus() I thought that was the end of it. Surely, this is exactly what I need?!? The spec made it sound so promising. I quickly switched to my about:blank tab that I use for trying things out, and ran it in the console.
> document.hasFocus()
< false
🤦🏽♀️🤦🏽♀️🤦🏽♀️
Neeeext!
Edit: document.hasFocus() may be the solution after all! As pointed out to me on Twitter, the problem above was that unlike I did with document.activeElement, I ran this synchronously in the console and it returned false because the console as the active window. An asynchronous log while I make sure the actual window is focused would do the trick.
The anti-climactic conclusion
Edit: I left this section in because the moral is still valid for other cases, but it looks like document.hasFocus() was the solution after all.
If you’re expecting this to end with a revelation of an amazing API that I had originally missed and addresses this, you will be disappointed. If there is such a silver bullet, I did not find it. Maybe someone will point it out to me after publishing this blog post, in which case I will update it so that you don’t struggle like I did.
But in my case, I simply gave up trying to find a general solution. Instead, I took advantage of the knowledge my code had in this specific situation: I knew what the other window was, and I primarily cared which one of the two (if any) had focus.
// Track whether presenter or projector is the active window
addEventListener("focus", _ => {
Inspire.isActive = true;
// If this window is focused, no other can be
if (Inspire.projector) {
Inspire.projector.Inspire.isActive = false;
}
else if (Inspire.presenter) {
Inspire.presenter.Inspire.isActive = false;
}
});
addEventListener("blur", _ => {
Inspire.isActive = false;
// If this window is not focused,
// we cannot make assumptions about which one is.
});
Given that the presenter view calls window.focus() after opening the projector view, in practice this was pretty bulletproof.
What’s the moral of this story?
- Sometimes simple questions do not have a good answer when it comes to the Web Platform
- If your code cannot answer the general question correctly in all cases, maybe it can answer a specific one that solves your particular problem, even if that leads to a less elegant solution.
That’s it folks.
Is the current tab active?
Today I ran into an interesting problem. Interesting because it’s one of those very straightforward, deceptively simple questions, that after a fair amount of digging, does not appear to have a definite answer (though I would love to be wrong!).
The problem was to determine if the current tab is active. Yes, as simple as that.
Why? (i.e. my use case)
I was working on my slide deck framework, Inspire.js. There is a presenter mode plugin, which spawns a new window with your slides (“projector view”), whereas your current window becomes a “presenter view”, with open notes, preview of the next slide, optional progress indicator for time etc.
However, this plugin was not very good. The two windows are synced, but only if you use presenter view to navigate slides. If you use the projector view to advance slides, the syncing breaks. Why would you use the projector mode? Many reasons, e.g. to interact with a live demo, or even play a video. If you have a live demo heavy presentation, you may even want to mirror your screen and only ever interact with the projector mode, while having the presenter mode on a secondary screen, just to look at.
The way the plugin worked was that every time the slide changed in the presenter view, it propagated the change in the projector view. To make the syncing bidirectional, it would be good to know if the current window is the active tab, and if so, propagate all slide navigation to the other one, regardless of which one is the projector view and which one is the presenter view.
And this, my friends, is how I ended up in this rabbit hole.
(Yes, there are other solutions to this particular problem. I could just always propagate regardless and have checks in place to avoid infinite loops. But that’s beside the point.)
What about the Visibility API?
In most resources around the Web, people were rejoicing about how the Visibility API makes this problem trivial. “Just use document.hidden!” people would gleefully recommend to others.
Yes, the Visibility API is great, when you want to determine whether the current tab is visible. That is not the same as whether it is active.
You may have two windows side by side, both visible, but only one of them is active. You may even have a window entirely obscuring another window, but you can still tab through to it and make it active. Active and visible are entirely orthogonal states, which are only loosely correlated.
In my use case, given that both the projector view and presenter view would be visible at all times, this is a no-go that doesn’t even solve a subset of use cases.
What about focus and blur events on window?
The other solution that was heavily recommended was using the focus and blur events on window. This does get us partway there. Indeed, when the current tab becomes active, the focus event fires. When another tab becomes active, the blur event fires.
Notice the emphasis on “becomes”. Events notify us about a state change, but they are no help for determining the current state. If we get a focus or blur event, we know whether our tab is active or not, but if we don’t get any, we simply don’t know. A tab can start off as active or not, and there is no way to tell.
How can a tab possibly start off as inactive? One easy way to reproduce this is to hit Return on the address bar and immediately switch to another window. The tab you just loaded just starts off as inactive and no blur event is ever fired.
What about document.activeElement?
The document.activeElement property will always return the currently focused element in a page. Can we use it to determine if a window currently has focus? Nope, cause that would be too easy.
Run setTimeout(() => console.log(document.activeElement), 2000) in the console and quickly switch windows. Return >2 seconds later and see what was logged. It’s the <body> element!
Wait, maybe we can assume that if the currently focused element is a <body> element then the current window is inactive? Nope, you get the same result in an active tab, if you simply haven’t focused anywhere.
What about document.hasFocus()?
When I discovered document.hasFocus() I thought that was the end of it. Surely, this is exactly what I need?!? The spec made it sound so promising. I quickly switched to my about:blank tab that I use for trying things out, and ran it in the console.
> document.hasFocus()
< false
🤦🏽♀️🤦🏽♀️🤦🏽♀️
Neeeext!
Edit: document.hasFocus() may be the solution after all! As pointed out to me on Twitter, the problem above was that unlike I did with document.activeElement, I ran this synchronously in the console and it returned false because the console as the active window. An asynchronous log while I make sure the actual window is focused would do the trick.
The anti-climactic conclusion
Edit: I left this section in because the moral is still valid for other cases, but it looks like document.hasFocus() was the solution after all.
If you’re expecting this to end with a revelation of an amazing API that I had originally missed and addresses this, you will be disappointed. If there is such a silver bullet, I did not find it. Maybe someone will point it out to me after publishing this blog post, in which case I will update it so that you don’t struggle like I did.
But in my case, I simply gave up trying to find a general solution. Instead, I took advantage of the knowledge my code had in this specific situation: I knew what the other window was, and I primarily cared which one of the two (if any) had focus.
// Track whether presenter or projector is the active window
addEventListener("focus", _ => {
Inspire.isActive = true;
// If this window is focused, no other can be
if (Inspire.projector) {
Inspire.projector.Inspire.isActive = false;
}
else if (Inspire.presenter) {
Inspire.presenter.Inspire.isActive = false;
}
});
addEventListener("blur", _ => {
Inspire.isActive = false;
// If this window is not focused,
// we cannot make assumptions about which one is.
});
Given that the presenter view calls window.focus() after opening the projector view, in practice this was pretty bulletproof.
What’s the moral of this story?
- Sometimes simple questions do not have a good answer when it comes to the Web Platform
- If your code cannot answer the general question correctly in all cases, maybe it can answer a specific one that solves your particular problem, even if that leads to a less elegant solution.
That’s it folks.