Why I can't wait for the :has() pseudo-class in CSS
Tags: css, webdev
Published: 2021-09-28 11:19:11

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](https://caniuse.com/css-has) 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>

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>

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;
    }

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.