The has() selector

The has() selector

Unlocking the Hidden Power of CSS: Say Goodbye to JS-only Dynamic Styles

This is the first in an ongoing series of posts called CSS Painkillers, covering seldom heard or used CSS concepts easing my development journey. So, if it’s your cup of tea, follow for more!

For the longest time, and especially after the advent of JS frameworks (yes React is a framework too), it has been easier to apply dynamic styles via JS. Dynamic styles are pivotal to modern web applications since they are the backbone of interactivity. They allow for immediate user feedback after events. Therefore, using application state to conditionally and dynamically apply CSS styles is a common practice that most frameworks facilitate. Let’s take the following example:

There is a table in which the first cell in each row contains a checkbox to select or deselect the row. If the checkbox is checked, you apply a style (let’s say a background color) to the entire row to highlight it. Since using CSS selectors exclusively would be a painful approach, you would use JS. In Vue, you would do something like this:

    <tr
    v-for="row in someData"
    :key="row.id"
    :class="{ 'row-selected': row.isSelected } //dynamically adding a class here based on whether row is selected"
  >
    <td>
      <input
        :id="`row-${row.id}`"
        type="checkbox"
        :name="row.data"
        :value="row.isSelected"
        @change="toggleRowSelection"
      />
    </td>
    <!-- Rest of the cells -->
  </tr>

Straightforward and easy to replicate across all frameworks. Until recently, I thought this was only way to accomplish this. I mean who would want to write CSS which looks like this?

/* Original background */
tr {
  background-color: white;
}

/* Change background color when checked, targeting siblings of the input checkbox */
input:checked + td + td + td,
input:checked + td + td,
input:checked + td,
input:checked {
  background-color: yellow;
}

Shivers. Anyway, things changed around 2022 when the eagerly waited has() selector gained major(but not complete) browser support. All Chromium based browsers support it, Firefox is yet to implement it and support on mobile browsers is so-so. For the current state of browser support, check this.

The has() selector, according to MDN, presents a way of selecting a parent element or a previous sibling element with respect to a reference element by taking a relative selector list as an argument. That’s …. surprisingly trivial, but hey, when you haven’t had something for the longest time (still waiting on Subgrid T_T), it’s a breath of fresh air.

Let’s see how the CSS using has() looks like:

tr:has(input:checked) {
  background-color: yellow;
}

And that’s it! A bit anti-climactic if you ask me. But this little guy is deviously powerful.

I just wanted to introduce the topic to you, hence kept the post short with a simple example. But the scope for using the selector is big. Study it well, and the world’s your oyster. And as always, remember it’s a Swiss Army knife, not a golden hammer.

So how do you see yourself using the has() selector? Let me know in the comments!