---
title: Can’t touch the DOM? Reach for :has() to style any element
teaser: Third-party component libraries come with restrictions, the :has() CSS pseudo-selector
  unlocks flexible style options without needing JavaScript.
tags: css,html,frontend,tips,design
author: Andrew Spencer
published_on: 2026-08-17
---

Using third-party component libraries like MUI, Ant Design, React Bootstrap, AG Grid, or Bryntum is a great way to move fast, especially for complex interactive elements like date pickers or interactive tables. There is a trade off though. It can be difficult to add custom styles or handle unique use cases because the library wants you to stay in their system. You often can’t safely adjust the DOM, and if you do, it requires JavaScript to add a custom class or wrapper which can be brittle.

For example, consider this imaginary input component we’ll assume you grabbed from a component library, meaning the HTML is likely uneditable.

```
<div class="input">
  <label>Email</label>
  <input required> <!-- "error" class added for error state -->
</div>
```

You want the `<label>` to turn red when the field has an error, but, the library only adds a class to the `<input>` for error states, which is a sibling _after_ the label. CSS doesn't allow us to target elements that come before. How would you approach this?

Likely your only option is some JavaScript to conditionally add a class to the container, or reconsider your style choices. Neither is a great option. But with :has you can handle this with CSS.

## :has to the rescue

Allow me to introduce (or reintroduce) you to the relatively new CSS pseudo-class [:has](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:has). This tiny but mighty tool allows us to traverse *up the DOM* to style parent or sibling elements and it became invaluable on a recent project when working on custom styles for elements in a JS component library.

Let’s say you’re adding an input element from a component library to your project. The HTML and hooks for customizing are pre-defined. But this locks you into the structure the library has set.

```html
<div class="input">
  <label>Email</label>
  <input required> <!-- "error" class added for error state -->
</div>
```

You want the `<label>` to turn red when the field has an error, but, for error states, the library only adds a class to the `<input>` sibling after the label. How would you approach this?

Without `:has`, Likely your only option is some JavaScript to conditionally add a class to the container, or reconsider your style choices. Neither is a great option. But with `:has` you can handle this with CSS.

```css
.input:has(.error) label {
  color: red;
}
```

## Practical applications

`:has` is [growing in popularity](https://2026.stateofcss.com/en-US) but personally I didn’t know where to start incorporating it into my process. I’d read articles and seen some incredible demos from folks like [Josh Comeau](https://www.joshwcomeau.com/css/has/) and [Ahmad Shadeed](https://ishadeed.com/article/css-has-guide/) but it never really clicked until working on a project with a third-party JS component library where I had limited control of the DOM but wanted unlimited styling flexibility.

Here’s just a few ways `:has` comes in handy.

### Check your parents

`:has` unlocks the ability to style parent elements, something that has been impossible with CSS until now. You’re no longer constrained to only select down (child) or forward (sibling after). You can go as far up the DOM as you want, allowing you to style *any* element in the DOM, from the root parent, to a child of a sibling.

![diagram of what you can select with or without `:has`. Without `:has` you cannot select a parent, sibling before, or child of a sibling before the selected element.](https://images.thoughtbot.com/guh966xdudoci0rk577kux8vw9ky_has-demo.jpg)

Adding `:has(something)` to a parent class allows you to add conditional styles like this menu where the presence of the sold out badge is what drives the styles of the sold out card. This is very useful for styling components in JS libraries that contain items that change state but don’t add any indication of state to a parent of the element you are trying to style.

<figure>
  <p class="codepen" data-height="300" data-pen-title="Style parents based on child content with :has()" data-version="2" data-default-tab="css,result" data-slug-hash="jEyXOVZ" data-user="iam_aspencer" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
    <span>See the Pen <a href="https://codepen.io/editor/iam_aspencer/pen/019fb9b2-f419-7daf-b1bf-aab08c8b4793">
    Style parents based on child content with :has()</a> by Andrew Spencer (<a href="https://codepen.io/iam_aspencer">@iam_aspencer</a>)
    on <a href="https://codepen.io">CodePen</a>.</span>
  </p>
  <script async src="https://public.codepenassets.com/embed/index.js"></script>
</figure>

### Detect next siblings

Working with table data can be tricky, especially if the table is from a component library. In this example, certain `<tr>` elements act as parents of what are technically sibling `<tr>` elements. `:has` provides more flexibility for styling specific rows, like this selector that flips the next sibling selector to target the  `<tr>` *before* an element with class `.parent`.

```css
tr:has(+ .parent) {
  border-bottom: 2px gray solid;
}
```

<figure>
  <p class="codepen" data-height="300" data-pen-title=":has selector" data-version="2" data-default-tab="css,result" data-slug-hash="PwWdyqZ" data-user="iam_aspencer" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
    <span>See the Pen <a href="https://codepen.io/editor/iam_aspencer/pen/019f95c9-c294-777b-a4a7-3476d48ca65f">
    :has selector</a> by Andrew Spencer (<a href="https://codepen.io/iam_aspencer">@iam_aspencer</a>)
    on <a href="https://codepen.io">CodePen</a>.</span>
  </p>
  <script async src="https://public.codepenassets.com/embed/index.js"></script>
</figure>

This effect could be achieved with `:not(:last-child)` and a border-top, but `:has` gives you more flexibility.

```css
.parent:not(:last-child) {
  border-top: 2px gray solid;
}
```

### Monitor state of child elements

Buttons that contain other buttons are forbidden with the [W3C HTML Spec](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button) but that doesn’t stop many component libraries from including interactive elements that contain other interactive elements. Here’s a selector I used to add a hover state to a grid cell when the buttons within it were not hovered.

```css
.grid-cell:hover:not(:has(.button:hover))
```

<figure>
  <p class="codepen" data-height="300" data-pen-title="Monitor state of child elements with :has()" data-version="2" data-default-tab="css,result" data-slug-hash="myRabPo" data-user="iam_aspencer" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
    <span>See the Pen <a href="https://codepen.io/editor/iam_aspencer/pen/019fb947-b7b0-7ed7-b905-91d73093634a">
    Monitor state of child elements with :has()</a> by Andrew Spencer (<a href="https://codepen.io/iam_aspencer">@iam_aspencer</a>)
    on <a href="https://codepen.io">CodePen</a>.</span>
  </p>
  <script async src="https://public.codepenassets.com/embed/index.js"></script>
</figure>

### Theme the whole page based on one component

`:has` isn’t limited to nearby elements. Because you can select all the way up to the root `<html>` or `<body>` tags, you can use the presence of a single component to drive page-wide styling. This is especially handy when the presence of a specific component should shift the entire page layout or color scheme.

```css
body:has(.special-component) {
  /* different styles for any element on the page */
}
```

For example, the above selector `body:has(.special-component)` would allow you to style any element on the page based on the presence of an element with the `special-component` class.

This becomes even more powerful with [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties). If a library uses custom properties for styles, as many do, you can override them conditionally without touching the library’s internal settings.

<figure
  <p class="codepen" data-height="300" data-pen-title="Restyle an entire page if a certain element is present using  :has" data-version="2" data-default-tab="css,result" data-slug-hash="rajgKzX" data-user="iam_aspencer" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
    <span>See the Pen <a href="https://codepen.io/editor/iam_aspencer/pen/01a000dd-3e45-74ec-9ab1-9e953a6e11d5">
    Restyle an entire page if a certain element is present using  :has</a> by Andrew Spencer (<a href="https://codepen.io/iam_aspencer">@iam_aspencer</a>)
    on <a href="https://codepen.io">CodePen</a>.</span>
  </p>
  <script async src="https://public.codepenassets.com/embed/index.js"></script>
<figure>

### A note on :has :not

Combining a CSS function like `:not` with `:has` increases what’s possible but just note the order here, `:not(:has)` vs `:has(:not)`. Why does this matter? [Polypane](https://polypane.app/blog/decoding-css-selectors-has-not-vs-not-has/) reminds us that functions and pseudo selectors expect to be attached to another element and if they aren’t, they add an implicit universal selector (`*`). For `:has` though, the selector you place inside is already applied to the child element so the selectors actually read:

* `div:has(*:not(button))` \- Inclusive: Select any div that contains at least one child element that is *not* a button
* `div:not(.div:has(button))` \- Targeted: Select any div that does not contain a button

## :has support?

[Browser support](https://caniuse.com/css-has) for `:has` is strong with every major browser now supporting it, but it’s not universal. It’s supported for 92.66% of global browser usage as of July 2026\. If you need broad support, wrap the `:has` selector in a `@support` and provide a fallback.

Next time you find yourself stuck with DOM elements you can’t adjust, `:has`… has your back.
