DEV Community

Charanjit Chana
Charanjit Chana

Posted on • Originally published at 1thingaweek.com

Why I can't wait for the :has() pseudo-class in CSS

This past week I had a great idea for a tiny PWA that would only use JavaScript to make sure it worked offline and nothing more. In the future, I could make use of localStorage to remember preferences, but beyond that there would be no need at all.

I finally carved out some time after a few days mulling over the idea and was determined to make it work with the :has() pseudo-class but it turns out that browser support is non-existent! I've seen it talked about for at least a year, maybe more so had assumed it would be safe to use.

I came to the realisation yesterday that I should give up on the CSS-only approach, for now. I can revisit once browser support starts ramping up and strip out any JavaScript that's no longer required.

Why I can't wait for :has() to come to CSS

The reason I can't wait for the :has() pseudo-class is that it's effectively a parent selector for CSS. I've been working professionally with CSS since 2005 and it's something I've wanted for the past 16 years at least. In the days before heavy JavaScript pages where things were just progressively enhanced, adding classes dynamically as you created the HTML was easy enough – even if it meant more logic than was ideal.

Then came along jQuery with its .parent() function that meant we could progressively enhance the page rather than having to fetch from the server every time. In reality, this meant twice the logic in some cases. Once for the canonical server-side rendered pages and another for how it should be handled in JavaScript.

When we finally get our hands on :has(), no longer will we need helper classes or will we need to specify which item is 'active' at a higher level than the developer wants it to be.

Here's an example of some HTML and where our classes are required right now. We have a menu where a sub-item is selected which needs to be highlighted, but I'd also like to highlight the parent item it belongs too but that also means adding a class:

<div>
    <ul>
        <li><a href="#">Item 1</a></li>
        <li><a href="#" class="active">Item 2</a>
            <ul>
                <li><a href="#" class="active">Item 1</a></li>
                <li><a href="#">Item 2</a></li>
             </ul>
        </li>
        <li><a href="#">Item 3</a></li>
        <li><a href="#">Item 4</a></li>
        <li><a href="#">Item 5</a></li>
    </ul>
</div>
Enter fullscreen mode Exit fullscreen mode

Once we get :has() in CSS though, our HTML changes a little and the parent no longer needs an explicit class to show that it's active:

<div>
    <ul>
        <li><a href="#">Item 1</a></li>
        <li><a href="#">Item 2</a>
            <ul>
                <li><a href="#" class="active">Item 1</a></li>
                <li><a href="#">Item 2</a></li>
             </ul>
        </li>
        <li><a href="#">Item 3</a></li>
        <li><a href="#">Item 4</a></li>
        <li><a href="#">Item 5</a></li>
    </ul>
</div>
Enter fullscreen mode Exit fullscreen mode

With just some CSS, we can now do the following to determine that the 'parent' has a child that meets the criteria:

li:has(.active) > a {
    font-weight: bold;
}
Enter fullscreen mode Exit fullscreen mode

It really would be that simple, now the parent of an element with the class "active" is targeted with its direct descendent styled.

I would personally prefer 'contains' or 'parent-of' instead of has for the pseudo-class name but I won't complain too much! This is yet another huge step in the right direction.

Top comments (2)

Collapse
 
taufik_nurrohman profile image
Taufik Nurrohman • Edited

It also more convenient to make custom checkbox/radio without extra markup.

label:has(input[type="checkbox"])::before {}
label:has(input[type="checkbox"]:checked)::before {}
Enter fullscreen mode Exit fullscreen mode
<label>
  <input type="checkbox">
  Yo!
</label>
Enter fullscreen mode Exit fullscreen mode

Another idea is to add dropdown arrow automatically to menu items that have submenu in it.

nav li:has(ul) > a::after {
  /* add dropdown menu arrow here */
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
cchana profile image
Charanjit Chana

It will be a game changer. For me it will mean leaner HTML and fewer server side decisions that need to be made just to decide if an element should be styled a certain way or not.