I'd like to talk about a very specific CSS behavior that might cause some headaches if you don't know (or forget!) about it.
Let's create a button:
<button>Click me</button>
I want this button to change its colors when it is pressed. With the :active
pseudo-class, that's easy.
button:active {
background-color: black;
color: white;
}
So far so good.
To improve accessibility, I want to apply the same effect when the button is focused with the keyboard or an assistive technology (and do nothing special if the focus comes from the mouse).
This is possible thanks to the new :focus-visible
pseudo-class. At the time of writing, it is supported without prefix by Chrome and Edge.
button:active,
button:focus-visible {
background-color: black;
color: white;
}
If you are on Chrome or Edge, you can try to focus the button with the tab key and you should see the :focus-visible
styles.
Here's the catch
Can you guess what will happen on other browsers? Obviously the :focus-visible
styles won't show, but any side effect, maybe?
We also lost the :active
styles!
The problem might seem obvious if you already know why this happens, but when I had to debug this in a much more complex CSS file, it boggled me for a good few minutes. And I don't think it is a well-known fact among beginners.
So how to put it?
If any part of a selector fails, the whole selector fails
In other words, if a browser doesn't know about the :focus-within
selector, it will throw out your entire CSS rule, including the :active
part.
You could say that CSS is a bit susceptible. If it doesn't like the type of olive you put on the pizza, it won't even consider eating it.
Here's the W3C specification:
When interpreting a list of declarations, unknown syntax at any point causes the parser to throw away whatever declaration it’s currently building, and seek forward until it finds a semicolon (or the end of the block). It then starts fresh, trying to parse a declaration again.
And so here's the fix you'll need to use: separate the "safe styles" and the crazy ones.
button:active {
background-color: black;
color: white;
}
button:focus-visible {
background-color: black;
color: white;
}
Now, :focus-visible
might fail, but it won't bother the :active
selector.
But why this behavior?
Not really resilient for a language that should adapt itself to many environments, is it?
CSS is broken! JavaScript is our only savior!
Well, there is actually a good reason for this, which is explained in an old document, the CSS 2.1 specification:
CSS 2.1 gives a special meaning to the comma (,) in selectors. However, since it is not known if the comma may acquire other meanings in future updates of CSS, the whole statement should be ignored if there is an error anywhere in the selector, even though the rest of the selector may look reasonable in CSS 2.1.
CSS designers displayed a great level of anticipation here. Indeed, here are some CSS selector that we'll be able to use sooner or later (in some cases, we already are):
.title:is(h1, h2, h3) {} /* h1.title, h2.title, h3.title */
.title:not(h1, h2) {} /* multiple exclusions */
section:has(img, figure) {} /* parent selector */
These selector contain commas, but these commas don't separate the first-level selectors.
If a browser doesn't understand :is
and tries to separate our first example, it could result in something like this:
.title:is(h1 /* What is this? Ignore */
h2 /* Style all the h2, even without the .title class! */
h3) /* Wut?*/
Instead, the browser is cautious. This has been discussed again in 2013, and kept the same way "due to Web-compat concerns".
Hacks
Some developers even used this as a hack to discriminate a specific browser.
Wanna apply a specific style to all browsers except IE? Just add a modern selector for the sole purpose of making IE ignore the rule.
.covfefe:not(div), h1 {
color: red;
}
Tadaa, the h1
will be red only if the browser also understands :not
.
With the correct combination of selectors, you could target or exclude any specific browser version. Which is of course very, very dirty, and hopefully not needed anymore 🙏
Top comments (0)