Peter Krieg's blog

CSS :where() Pseudo Class

I had a strange encounter with some CSS recently that I wanted to share. At Logikcull, we have a mixture of legacy sass styles, and newer react code using styled-components. Sometimes, there are legacy styles defined to generic elements, that are undesired for certain components we have. Take the following contrived example below:

There are 3 inputs - the first one is just a generic input. The second one has a class of some-component - this is to represent a component you might have in a react app, like TextField. The 3rd input is a “one-off” which could be some legacy input with custom styles (in this case, a red border).

This was the basic situation we had at logikcull. We needed a way to exclude certain global styles from being applied to our shared components. That style in the example shown is the blue border (which of course no one in their right mind would put in real life 😂). What’s an easy way to exclude the global input styles from a shared component?

My first attempt at this was to use the :not() pseudo class to exclude the input component. So now, we exclude any input with class of some-component, and we get our desired result of the 2nd input (component) not having the blue border. But wait - why did our 3rd input have its red border removed? What happened there?

It turns out that the contents of the :not() operator count towards calculating specificity. This means that previously the CSS rule for applying the blue border was simply one element (the specificity notation would be 0-0-1). The one off rule applying the red border is a single class name, with specificity of 0-1-0. But after applying the :not(), the specificity of the blue border rule is now 0-1-1, which now “wins” vs the red border rule.

Okay, so how can we provide some exclusions to a CSS rule without impacting specificity and having possible side effects? Enter - the :where() operator!

Now the last input has a red border as desired! This is because :where() always has 0 specificity. It’s now supported by all major browsers, so I’d always recommend it when dealing with a large codebase of styles. It’s a safe way to add exclusions without touching specificity and possibly introducing side effects. It’s a bit more verbose syntax, but totally worth it in my opinion.

This example may seem a bit silly, but this exact thing actually happened to me at Logikcull. Our newer react codebase would sometimes have undesired styles applied from the legacy sass styles. Instead of digging into each component and resetting the rule, I went to the source of the legacy styles and found the relevant rule, and added the :not() rule. But then this bumped up the specificity and introduced several small bugs from styles changing. By moving the :not() to :where(:not()) everything has been smooth sailing since then!