DEV Community

Cover image for CSS :has(.parent-selectors) 👪

CSS :has(.parent-selectors) 👪

Ingo Steinke, web developer on December 21, 2021

I wonder why I have to follow "Tech Twitter" to find out the good news, so I'm the one to write a short post here on dev.to to celebrate a new CSS ...
Collapse
 
timhuang profile image
Timothy Huang

A really good introducing post to explain :has() (pseudo-class selector) and very useful.
Thanks!

Collapse
 
ingosteinke profile image
Ingo Steinke, web developer

Months later, after many people used to argue that there was no real world use case for has:, suddenly "TechTwitter" is full of posts about this great new has: property and that everyone should definitely try it and open up for the new possibilities.
Must have been some great CSS conference that I missed unfortunately, but still happy that CSS is moving forward and still (or again) fascinating web designers and developers.

Collapse
 
ingosteinke profile image
Ingo Steinke, web developer

Be aware that you might not have a browser to test parent selectors early in 2022 yet. Safari updates currently don't ship or install on older Apple operating systems, and Gnome Web, although based on a WebKitGTK, which I understand to be a based on a fork of AppleWebkit, did not support parent selectors in WebKitGTK 2.34.3 yet either, and neither did Vivaldi and Google-Chrome as of February 2022.

Collapse
 
iainsimmons profile image
Iain Simmons

Can you please share more of the code surrounding your use case? Because it seems like the only thing you add to the parent is width: 100%, and the rest you are nesting in a h1 selector anyways.

A trick I use for CMS generated HTML (i.e. everything without classes) is to use a :not([class]) selector. Then anything else, e.g. an unordered list or hyperlinks in a nav menu, just need any class added (or manually override the styles).

Collapse
 
ingosteinke profile image
Ingo Steinke, web developer

Here is an example (similar markup from a demo shop, with h2 headings. The top-level cms-section.cms-section-default only differ by their pos-n position classes which have no benefit compared to nth-child.

Any other distinction, even the content id's, can only be made on a descendent level.

The structure is as follows:

<div class="cms-sections">
    <div class="cms-section pos-0 cms-section-default">
        <div class="cms-block-container" style="/* some inline style */">
            <div class="cms-block-container-row row cms-row">
                <div class="col-12" data-cms-element-id="somecrypticidstring">
                    <div class="cms-element-text">
                        <h2>Headline (not every CMS content type has a headline</h2>
                        <p>Lorem ipsum...</p>
                    </div> 
                </div>
            </div>
        </div> 
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Here is a screenshot as well

screenshot of shopware CMS markup with a lot of nested divs having generic class names

Collapse
 
iainsimmons profile image
Iain Simmons

Thanks, but sorry I still don't see the value here.

Your CSS only applied width: 100% in the :has selector, which a div would have by default as a block level element.

Otherwise, if targeting the h2 or p, you could do that with descendent selectors or the :not([class]) as I suggested in my previous comment.

Perhaps a better example could be for forms or something, where you could combine with the :invalid pseudo-class or similar to change the parent div styles:

.form-control:has(:invalid) {
  background-color: rgb(220 53 69 / 0.25);
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
ingosteinke profile image
Ingo Steinke, web developer

Thanks for the form example!

Still don't see how you would have done it with not when all the div elements are undistinguishable by their class names. Of course, a div should be a block level element by default. You are lucky you didn't have to deal with the existing CSS. Using descendant selectors was what I did in the end, still didn't find it elegant or robust, probably it will break after the next framework update.

Better way would be if the customers content editors would choose different types of block templates which could then be given distinct class names.
Complex code should always arise suspicion that something might be conceptually wrong by design and could be solved at the problem's root cause. That's what I meant by "hotfixing a hotfix" in CSS (oh, and don't even start to count the number of !important in the existing style sheets).

Thread Thread
 
iainsimmons profile image
Iain Simmons

Sorry, I didn't make that clear. The :not([class]) would be for targeting the paragraphs or headings that come from the CMS or whatever, where you don't have the ability to add or target classes.

e.g.

body {
  color: #000;
}

p:not([class]) {
  font-size: 1rem;
  color: #222;
}

.lead {
  font-size: 1.25rem;
}
Enter fullscreen mode Exit fullscreen mode
<main>
  <p class="lead">Lorem ipsum...</p>
  <!-- other content not generated by the CMS -->

<div class="cms-sections">
    <div class="cms-section pos-0 cms-section-default">
        <div class="cms-block-container" style="/* some inline style */">
            <div class="cms-block-container-row row cms-row">
                <div class="col-12" data-cms-element-id="somecrypticidstring">
                    <div class="cms-element-text">
                        <h2>Headline</h2>
                        <p>CMS Content. Lorem ipsum...</p>
                    </div> 
                </div>
            </div>
        </div> 
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

The lead paragraph would be black, inheriting the color from the body.

So the not selector makes them act like global element styles that you opt out of by simply adding a class to the HTML element (even a blank string).

The assumption is that anything you want custom styling for that isn't regular body copy content (from a CMS or similar), you probably have control over the HTML and would normally be adding classes to style them anyways.

But anyways, this is obviously a whole other conversation! 🙂

Collapse
 
ingosteinke profile image
Ingo Steinke, web developer • Edited

Updated my article quoting Byungwoo Lee, Eric Meyer, Chris Coyier and Jonathan Snook on performance issues of parent selectors and how the Blink team finally solved them.
Included jQuery code as it's still the only working polyfill I found.
Anyone know a vanilla JS solution?

Collapse
 
yutamago profile image
Yutamago

Last time I checked, this was still a working draft. It doesn't make sense to implement this for any browser as long as the details are still not set in stone.
I'm waiting for this feature too. Had a use case for it yesterday and had to reside to a workaround instead. :(

Collapse
 
ingosteinke profile image
Ingo Steinke, web developer

As I understand, the implementation details have been fixed now, unless the working proof of concept reveals any flaw that has been overlooked until now. But how to evaluate if nobody used it in real life?

I will use this as a kind of progressive-enhancement addition to the every-other-browser fallback code once I get my hands on a browser that actually supports :has, but neither my real Apple devices (an iPhone 6+ and a MacBook from 2010) nor GnomeWeb (which I hoped to be some kind of "Safari for Linux") allow me to test it right now. I don't have a new MacBook, no Hackintosh macOS in a VM, and I don't want to pay for BrowserStack only to test an upcoming feature that's not even supported by Chrome yet either.

Still don't understand that Apple restricts browsers on older operating systems when they don't even ship their own browser in the latest version. At least this is one thing Microsoft got right with a new Edge, after making all the negative experience they were able to gather after years of trying to get rid of outdated Internet Explorer versions.

Agree, that it's probably to early to use parent selectors in real life, even two months after originally celebrating the news.

Collapse
 
ingosteinke profile image
Ingo Steinke, web developer

Thanks @lukeshiru ! That looks very useful. So we can use has in css and add querySelectorAllWithHas as a progressive enhancement for any browser without has support.

Collapse
 
ingosteinke profile image
Ingo Steinke, web developer

Another update: added an actual use case I found in my git history, as many people seemed to have a hard time imagining any useful scenario for :has() apart from the simple MDN example.