What happens when you write an invalid CSS selector? (bad things)
Published: 2020-12-07 08:19:09

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:

```html
<button>Click me</button>
```

I want this button to change its colors when it is pressed. With the `:active` pseudo-class, that's easy.

```css
button:active {
  background-color: black;
  color: white;
}
```

{% codepen https://codepen.io/bcalou/pen/OJXYNag %}

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](https://caniuse.com/?search=focus-visible).

```css
button:active,
button:focus-visible {
  background-color: black;
  color: white;
}
```

{% codepen https://codepen.io/bcalou/pen/bGeyyPo %}

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.

![Focus within styles](https://dev-to-uploads.s3.amazonaws.com/i/hce2hf2erpl0nkcglwws.gif)
<figcaption>This is a great visual help if you don't use a mouse</figcaption>

# 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!

![There are no styles on Firefox](https://dev-to-uploads.s3.amazonaws.com/i/xxz2fpdqa6v01brkjcca.gif)
<figcaption>The :active styles don't show on Firefox, despite the browser perfect understanding of the :active selector. So sad.</figcaption>

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.

![Garbage truck](https://dev-to-uploads.s3.amazonaws.com/i/dzaxbvr99oj5mc2is4sx.gif)
<figcaption>CSS has no time to waste with your crazy selectors</figcaption>

Here's the W3C [specification](https://www.w3.org/TR/css-syntax-3/#error-handling):

<blockquote> 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.</blockquote>

And so here's the fix you'll need to use: separate the "safe styles" and the crazy ones.

```css
button:active {
  background-color: black;
  color: white;
}

button:focus-visible {
  background-color: black;
  color: white;
}
```

{% codepen https://codepen.io/bcalou/pen/oNzbByo %}

Now, `:focus-visible` might fail, but it won't bother the `:active` selector.

# But why this behavior?

<blockquote>Not really resilient for a language that should adapt itself to many environments, is it?</blockquote>

<blockquote>CSS is broken! JavaScript is our only savior!</blockquote>

Well, there is actually a good reason for this, which is explained in an old document, the [CSS 2.1 specification](hhttps://www.w3.org/TR/CSS21/syndata.html#rule-sets):

<blockquote>
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.</blockquote>

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):

```css
.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:

```css
.title:is(h1 /* What is this? Ignore */
h2 /* Style all the h2, even without the .title class! */
h3) /* Wut?*/
```

![A man with a chainsaw](https://dev-to-uploads.s3.amazonaws.com/i/t2yxcsm15eqcy1wr1j9u.gif)
<figcaption>The CSS chainsaw massacre</figcaption>

Instead, the browser is cautious. This has been discussed again in 2013, and kept the same way ["due to Web-compat concerns"](https://lists.w3.org/Archives/Public/www-style/2013Apr/0246.html).

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

```css
.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 🙏