<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thoughtbot="https://thoughtbot.com/feeds/">
  <title>Giant Robots Smashing Into Other Giant Robots</title>
  <subtitle>Written by thoughtbot, your expert partner for design and development.
</subtitle>
  <id>https://robots.thoughtbot.com/</id>
  <link href="https://thoughtbot.com/blog"/>
  <link href="https://feed.thoughtbot.com" rel="self"/>
  <updated>2026-08-17T00:00:00+00:00</updated>
  <author>
    <name>thoughtbot</name>
  </author>
<entry>
  <title>Can’t touch the DOM? Reach for :has() to style any element</title>
  <link rel="alternate" href="https://thoughtbot.com/blog/can-t-touch-the-dom-reach-for-has"/>
  <author>
    <name>Andrew Spencer</name>
  </author>
  <id>https://thoughtbot.com/blog/can-t-touch-the-dom-reach-for-has</id>
  <published>2026-08-17T00:00:00+00:00</published>
  <updated>2026-08-14T19:21:23Z</updated>
  <content type="html">&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;For example, consider this imaginary input component we’ll assume you grabbed from a component library, meaning the HTML is likely uneditable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="input"&amp;gt;
  &amp;lt;label&amp;gt;Email&amp;lt;/label&amp;gt;
  &amp;lt;input required&amp;gt; &amp;lt;!-- "error" class added for error state --&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You want the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; to turn red when the field has an error, but, the library only adds a class to the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; for error states, which is a sibling &lt;em&gt;after&lt;/em&gt; the label. CSS doesn’t allow us to target elements that come before. How would you approach this?&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id="has-to-the-rescue"&gt;
  
    :has to the rescue
  
&lt;/h2&gt;

&lt;p&gt;Allow me to introduce (or reintroduce) you to the relatively new CSS pseudo-class &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:has"&gt;:has&lt;/a&gt;. This tiny but mighty tool allows us to traverse &lt;em&gt;up the DOM&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- "error" class added for error state --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You want the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; to turn red when the field has an error, but, for error states, the library only adds a class to the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; sibling after the label. How would you approach this?&lt;/p&gt;

&lt;p&gt;Without &lt;code&gt;:has&lt;/code&gt;, 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 &lt;code&gt;:has&lt;/code&gt; you can handle this with CSS.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.input&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.error&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="practical-applications"&gt;
  
    Practical applications
  
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;:has&lt;/code&gt; is &lt;a href="https://2026.stateofcss.com/en-US"&gt;growing in popularity&lt;/a&gt; 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 &lt;a href="https://www.joshwcomeau.com/css/has/"&gt;Josh Comeau&lt;/a&gt; and &lt;a href="https://ishadeed.com/article/css-has-guide/"&gt;Ahmad Shadeed&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;Here’s just a few ways &lt;code&gt;:has&lt;/code&gt; comes in handy.&lt;/p&gt;
&lt;h3 id="check-your-parents"&gt;
  
    Check your parents
  
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;:has&lt;/code&gt; 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 &lt;em&gt;any&lt;/em&gt; element in the DOM, from the root parent, to a child of a sibling.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://images.thoughtbot.com/guh966xdudoci0rk577kux8vw9ky_has-demo.jpg" alt="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."&gt;&lt;/p&gt;

&lt;p&gt;Adding &lt;code&gt;:has(something)&lt;/code&gt; 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.&lt;/p&gt;

&lt;figure&gt;
  &lt;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;"&gt;
    &lt;span&gt;See the Pen &lt;a href="https://codepen.io/editor/iam_aspencer/pen/019fb9b2-f419-7daf-b1bf-aab08c8b4793"&gt;
    Style parents based on child content with :has()&lt;/a&gt; by Andrew Spencer (&lt;a href="https://codepen.io/iam_aspencer"&gt;@iam_aspencer&lt;/a&gt;)
    on &lt;a href="https://codepen.io"&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
  &lt;/p&gt;
  &lt;script async src="https://public.codepenassets.com/embed/index.js"&gt;&lt;/script&gt;
&lt;/figure&gt;
&lt;h3 id="detect-next-siblings"&gt;
  
    Detect next siblings
  
&lt;/h3&gt;

&lt;p&gt;Working with table data can be tricky, especially if the table is from a component library. In this example, certain &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; elements act as parents of what are technically sibling &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; elements. &lt;code&gt;:has&lt;/code&gt; provides more flexibility for styling specific rows, like this selector that flips the next sibling selector to target the  &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; an element with class &lt;code&gt;.parent&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(+&lt;/span&gt; &lt;span class="nc"&gt;.parent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nx"&gt;gray&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
  &lt;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;"&gt;
    &lt;span&gt;See the Pen &lt;a href="https://codepen.io/editor/iam_aspencer/pen/019f95c9-c294-777b-a4a7-3476d48ca65f"&gt;
    :has selector&lt;/a&gt; by Andrew Spencer (&lt;a href="https://codepen.io/iam_aspencer"&gt;@iam_aspencer&lt;/a&gt;)
    on &lt;a href="https://codepen.io"&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
  &lt;/p&gt;
  &lt;script async src="https://public.codepenassets.com/embed/index.js"&gt;&lt;/script&gt;
&lt;/figure&gt;

&lt;p&gt;This effect could be achieved with &lt;code&gt;:not(:last-child)&lt;/code&gt; and a border-top, but &lt;code&gt;:has&lt;/code&gt; gives you more flexibility.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.parent&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:last-child&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nx"&gt;gray&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="monitor-state-of-child-elements"&gt;
  
    Monitor state of child elements
  
&lt;/h3&gt;

&lt;p&gt;Buttons that contain other buttons are forbidden with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button"&gt;W3C HTML Spec&lt;/a&gt; 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.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.grid-cell&lt;/span&gt;&lt;span class="nd"&gt;:hover:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.button&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
  &lt;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;"&gt;
    &lt;span&gt;See the Pen &lt;a href="https://codepen.io/editor/iam_aspencer/pen/019fb947-b7b0-7ed7-b905-91d73093634a"&gt;
    Monitor state of child elements with :has()&lt;/a&gt; by Andrew Spencer (&lt;a href="https://codepen.io/iam_aspencer"&gt;@iam_aspencer&lt;/a&gt;)
    on &lt;a href="https://codepen.io"&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
  &lt;/p&gt;
  &lt;script async src="https://public.codepenassets.com/embed/index.js"&gt;&lt;/script&gt;
&lt;/figure&gt;
&lt;h3 id="theme-the-whole-page-based-on-one-component"&gt;
  
    Theme the whole page based on one component
  
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;:has&lt;/code&gt; isn’t limited to nearby elements. Because you can select all the way up to the root &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 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.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.special-component&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* different styles for any element on the page */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For example, the above selector &lt;code&gt;body:has(.special-component)&lt;/code&gt; would allow you to style any element on the page based on the presence of an element with the &lt;code&gt;special-component&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;This becomes even more powerful with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties"&gt;CSS custom properties&lt;/a&gt;. If a library uses custom properties for styles, as many do, you can override them conditionally without touching the library’s internal settings.&lt;/p&gt;

&lt;p&gt;&lt;figure 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;"&gt;
    &lt;span&gt;See the Pen &lt;a href="https://codepen.io/editor/iam_aspencer/pen/01a000dd-3e45-74ec-9ab1-9e953a6e11d5"&gt;
    Restyle an entire page if a certain element is present using  :has&lt;/a&gt; by Andrew Spencer (&lt;a href="https://codepen.io/iam_aspencer"&gt;@iam_aspencer&lt;/a&gt;)
    on &lt;a href="https://codepen.io"&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
  &lt;/figure&gt;&lt;/p&gt;
  &lt;script async src="https://public.codepenassets.com/embed/index.js"&gt;&lt;/script&gt;
&lt;figure&gt;
&lt;h3 id="a-note-on-has-not"&gt;
  
    A note on :has :not
  
&lt;/h3&gt;

&lt;p&gt;Combining a CSS function like &lt;code&gt;:not&lt;/code&gt; with &lt;code&gt;:has&lt;/code&gt; increases what’s possible but just note the order here, &lt;code&gt;:not(:has)&lt;/code&gt; vs &lt;code&gt;:has(:not)&lt;/code&gt;. Why does this matter? &lt;a href="https://polypane.app/blog/decoding-css-selectors-has-not-vs-not-has/"&gt;Polypane&lt;/a&gt; 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 (&lt;code&gt;*&lt;/code&gt;). For &lt;code&gt;:has&lt;/code&gt; though, the selector you place inside is already applied to the child element so the selectors actually read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;div:has(*:not(button))&lt;/code&gt; - Inclusive: Select any div that contains at least one child element that is &lt;em&gt;not&lt;/em&gt; a button&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;div:not(.div:has(button))&lt;/code&gt; - Targeted: Select any div that does not contain a button&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="has-support"&gt;
  
    :has support?
  
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://caniuse.com/css-has"&gt;Browser support&lt;/a&gt; for &lt;code&gt;:has&lt;/code&gt; 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 &lt;code&gt;:has&lt;/code&gt; selector in a &lt;code&gt;@support&lt;/code&gt; and provide a fallback.&lt;/p&gt;

&lt;p&gt;Next time you find yourself stuck with DOM elements you can’t adjust, &lt;code&gt;:has&lt;/code&gt;… has your back.&lt;/p&gt;

&lt;aside class="related-articles"&gt;&lt;h2&gt;If you enjoyed this post, you might also like:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/rust-doesn-t-have-named-arguments-so-what"&gt;Rust Doesn’t Have Named Arguments. So What?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/button-element-tutorial"&gt;Are you absolutely sure you know how to use the button element?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/positioning"&gt;Positioning elements on the web with CSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/aside&gt;
&lt;/figure&gt;</content>
  <summary>Third-party component libraries come with restrictions, the :has() CSS pseudo-selector unlocks flexible style options without needing JavaScript.</summary>
  <thoughtbot:auto_social_share>true</thoughtbot:auto_social_share>
</entry>
</feed>
