Simpler CSS Selectors With :is()
Tags: css, webdev, html
Published: 2023-01-19 19:32:52

## Simplifying redundant selectors

If you’ve got redundant CSS selectors like this:

```css
/* 😕 */
.active a,
.active button,
.active label {
  color: steelblue;
}
```

Did you know that you can rewrite it like this?

```css
/* 🤩 */
.active :is(a, button, label) {
  color: steelblue;
}
```

That’s right, the [:is() pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:is) is built into plain-ole CSS now.

You can use `:is()` to group any part of a selector, for instance, you could similarly transform this:

```css
.section h2,
.aside h2,
.nav h2 {
  color: steelblue;
}
```

into just this:

```css
:is(.section, .aside, .nav) h2 {
  color: steelblue;
}
```

But `:is()` is not just useful for parents and children - it can also select multiple adjoining selectors as well, like:

```css
button:is(:focus, :hover, :active) {
  color: steelblue;
}

button:is(.active, .pressed) {
  color: lightsteelblue;
}
```

Which behave equivalent to:

```css
button:focus, button:hover, button:active {
  color: steelblue;
}

button.active, button.pressed {
  color: lightsteelblue;
}
```

## Comparison to `:where()`

`:where()` is a very similar pseudo-class to `:is()` and is worth being aware of as well. They look very similar:

```css
:where(.section, .aside, .nav) h2 {
  color: steelblue;
}
```

But the difference is that `:where` has a [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) of 0, whereas `:is()` always takes on the specificity of the most specific selector in the list.

So, do you know what color the button will be in this CSS?

```css
:is(html) button {
  color: red;
}

:where(html) button {
  color: blue;
}
```

In the above example, although the block starting with `:where()` is below the block starting with `:is()`, the `:is()` block has a higher specificity (1 for the `html` tag, + 1 for the `button`), vs the block below it that has a lesser specificity (0 for `html` because it is within `:where`, and +1 for `button`).

## Comparison to `:has()`

A related, but very different, pseudo-class is `:has()`. `:has()` allows you to select a parent that *contains* a child matching a selector (or set of selectors).

An example use case of `:has()` is to not underline links that contain an image or video:

```css
a { text-decoration: underline }

/* Links are underlined, unless they contain an image or video */
a:has(img, video) {
  text-decoration: none;
}
```

Now, if by default our `a` tags have underlined text, but we have an image or video inside one, the underlining will be removed for any matching anchor elements.

You can also combine this with `:is()`

```css

:is(a, button):has(img, video) {
  text-decoration: none;
}**Note though that :has() is [*not* yet supported in all major browsers](https://developer.mozilla.org/en-US/docs/Web/CSS/:has#browser_compatibility), so use with caution.**
```

## Do we still need preprocessors?

Now you may be reading this and saying “SCSS can do this!”, and you may even prefer its syntax:

```css
.active {
  button, label, a {
    color: steelblue;
  }
}
```

And you would be right, this is quite elegant. But it seems like every day CSS natively gets features that we once needed SCSS (or other preprocessors) for. 

[CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) has also been another incredible addition to CSS itself, that it begs the question when or how often you really need preprocessors anymore:

```css
/* Plain ole modern CSS baby */
.active :is(a, button, label) {
  --color: steelblue;
  color: var(--steelblue);
}
```

That’s not to say preprocessors don’t still have their use cases and merits.

But I think at one point in time they were truly mandatory for handling any non-trivial CSS (elegantly, at least), and now that’s not as much the case anymore.

## One last surprise…

Just to leave things on a high note, I want to point out that the future of CSS continues to look bright. The CSS Working Group is [actively working on adding nested selectors](https://webkit.org/blog/13607/help-choose-from-options-for-css-nesting-syntax/) directly to CSS. They are actively deciding between 3 possible syntaxes (known as options “3, “4”, and “5”):

```css
/* Option 3 */
article {
  font-family: avenir;
  & aside {
    font-size: 1rem;
  }
}

/* Option 4 */
article {
  font-family: avenir;
} {
  aside {
    font-size: 1rem;
  }
}

/* Option 5 */
@nest article {
  & {
    font-family: avenir;
  }
  aside {
    font-size: 1rem;
  }
}
```

Which do you like best? And does your choice match the winner in the [official poll](https://webkit.org/blog/13607/help-choose-from-options-for-css-nesting-syntax/)?

Spoiler alert - #3 won the poll, so we may be getting a very SCSS-like nesting syntax as an icing on the cake to CSS soon, assuming the CSSWG’s chooses the poll winner.

And yes, I agree, Option 4 is an absolute abomination that needs to burn in fire.

## Browser support

### Browser support for `:is()` and `:where()`

The `:is()` and `:where()` pseudo-classes are supported in all major browsers:

![Browser support table screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cqzhgm80ds5gzsm4ujg7.png)

Source: [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/:is)

### Browser support for `:has()`

Note that the `:has()` pseudo-class that we touched on here does not have the same level of support, so use `:has()` with caution:

![Browser support table screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0e1s8ol1g7h98r9s96i.png)

Source: [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/:has)

## Conclusion

Modern CSS is awesome, and only keeps getting better. Next time you’ve got redundant selectors in your code - don’t forget to reach for that handy `:is()` pseudo-class.

## About me

Hi! I'm [Steve](https://twitter.com/Steve8708?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor), CEO of [Builder.io](https://www.builder.io/).

We make a way to drag + drop with your components to create pages and other CMS content on your site or app, visually.

You can read more about how this can improve your workflow [here](https://www.builder.io/blog/headless-cms-workflow).

You may find it interesting or useful:

![Builder.io demo](https://res.cloudinary.com/practicaldev/image/fetch/s--Y1qvC5Xj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/au5t8ti43cci5n6t6cfb.gif)