The CSS :has
selector helps you select elements that contain elements that match the selector you pass into the :has()
function. It’s essentially a “parent” selector, although far more useful than just that. For example, imagine being able to select all <div>
s but only when they contain a <p>
. That’s what we can do:
div:has(p) {
background: red;
}
/*
<div> <!-- selected! -->
<p></p>
<div>
<div></div> <!-- not selected -->
<div> <!-- not selected -->
<section></section>
</div>
*/
Although it’s not supported in any browser as I write, it has now dropped in Safari Technical Preview 137, so it’s starting to happen!
Pretty darn handy right! Here’s another example. Say you want space after your headers. Of course! A bit of margin-block-end
on your h2
should do it. But… what if there is a subtitle? Now we can select a parent on the condition a subtitle is present and adjust the spacing.
h2,
.subtitle {
margin: 0 0 1.5rem 0;
}
.header-group:has(h2):has(.subtitle) h2 {
margin: 0 0 0.2rem 0; /* reduce spacing on header, because subtitle will handle it */
}
/*
<div class="header-group">
<h2>Blog Post Title</h2> <!-- main spacing applied here -->
</div>
<div class="header-group">
<h2>Blog Post Title</h2>
<div class="subtitle"> <!-- main spacing applied here -->
This is a subtitle
</div>
</div>
*/
Notice how the example chains two :has()
selectors: .header-group:has(h2):has(.subtitle)
. It’s worth calling out that this is different than chaining items in the selector’s argument list, e.g. .header-group:has(h2 .subtitle)
. In the example, the chained :has()
selectors say, “Select this <h2>
element only if the .header-group
has an <h2>
element and that <h2>
has a .subtitle
.” Conversely, the argument list example says, “Select this <h2>
element only if the .header-group
has another element that is an <h2>
element with a class of .subtitle
,” which does not work in the example.
The way I think about :has
is this: it’s a parent selector pseudo-class. That is CSS-speak for “it lets you change the parent element if it has a child or another element that follows it.” This might feel weird! It might break your mental model of how CSS works. This is how I’m used to thinking about CSS:
.parent .child {
color: red;
}
You can only style down, from parent to child, but never back up the tree. :has
completely changes this because up until now there have been no parent selectors in CSS and there are some good reasons why. Because of the way in which browsers parse HTML and CSS, selecting the parent if certain conditions are met could lead to all sorts of performance concerns.
If I sit down and think about all the ways I might use :has
today, I sort of get a headache. It would open up this pandora’s box of opportunities that have never been possible with CSS alone.
Another example: let’s say we want to only apply styles to links that have images in them:
a:has(> img) {
border: 20px solid white;
}
This would be helpful from time to time. I can also see :has
being used for conditionally adding margin and padding to elements depending on their content. That would be neat.
The :has
selector is part of the CSS Selectors Level 4 specification which is the same spec that has the extremely useful :not
pseudo-class.
You could argue that the CSS :has
selector is more powerful than just a “parent” selector, which is exactly what Bramus has done! Like in the subheadings example above, you aren’t necessarily ultimately selecting the parent, you might select the parent in a has-condition, but then ultimately select a child element from there.
/* Matches <figure> elements that have a <figcaption> as a child element */
figure:has(figcaption) { … }
/* Matches <img> elements that is a child of a <figure> that contains a <figcaption> child element */
figure:has(figcaption) img { … }
There you can quickly see that the second selector is selecting a child <img>
, not just the parent of the <figcaption>
.
Selector List
You can chain it:
article:has(h2):has(ul) {
}
Or give it a selector list:
article:has(h2, ul) {
}
And the list is forgiving: The list is no longer “forgiving” after the W3C adopted a resolution in December 2020 in response to a reported issue. So, if the selector list contains even one invalid argument, the entire list is ignored:
/* 👎 */
article:has(h2, ul, ::-blahdeath) {
/* ::blahdeath is invalid, making the entire selector invalid. */
}
A workaround is to nest a more forgiving selector in there, such as :is()
or :where()
:
/* 👍 */
article:has(:where(h2, ul, ::-blahdeath)) {
/* :where is a forgiving selector, making this valid. */
}
Testing for Support
@supports(selector(:has(p))) {
/* Supported! */
}
:not()
selector is part of the same spec…
The Unlike :has
, :not
does have pretty decent browser support and I used it for the first time the other day:
ul li:not(:first-of-type) {
color: red;
}
That’s great I also love how gosh darn readable it is; you don’t ever have to have seen this line of code to understand what it does.
Another way you can use :not
is for margins:
ul li:not(:last-of-type) {
margin-bottom: 20px;
}
So every element that is not the last item gets a margin. This is useful if you have a bunch of elements in a card, like this:
:is()
and :where()
… and also CSS Selectors Level 4 is also the same spec that has the :is
selector that can be used like this today in a lot of browsers:
:is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) {
color: #BADA55;
}
/* ... which would be the equivalent of: */
section h1, section h2, section h3, section h4, section h5, section h6,
article h1, article h2, article h3, article h4, article h5, article h6,
aside h1, aside h2, aside h3, aside h4, aside h5, aside h6,
nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 {
color: #BADA55;
}
More Info
So that’s it! :has
should be quite useful to use soon, and its cousins :is
and :not
can be fabulously helpful already and that’s only a tiny glimpse — just three CSS pseudo-classes — that are available in this new spec.
- Adrian Bece — Meet
:has
, A Native CSS Parent Selector (And More) - Bramus Van Damme — The CSS
:has()
selector is way more than a “Parent Selector” - Michelle Barker — :has() Has Landed in Safari
Definitely feels like reading a CSS selector with
:has()
will take a while to get used to.Although, really excited about
:where()
!:how()
would that work?Honestly just being able to style links that wrap images would be a godsend; so many sites I build have these overly fancy link styles that fall apart when the content is an inline image. I’ve had to write various fixes that patch the markup or require some wrapper class to let me target the links that are intended to be sans-frills clickable images.
Yeah, that is a common use for
has
, but let’s be honest it would probably take years to have browser support, and that will be if it will make it through at all.That syntax seems very inconsistent though.
:has(p)
I can understand as containing a<p>
element. However:has(+ div)
as being followed by a<div>
does not seem consistent. I’d expect it to contain a<div>
element that is preceded by any other element. How would that be expressed then? By:has(* + div)
?Same thing here, initially I thought of the has as an hierarchy selector, for whatever is inside the main element but it’s more of something that applies to the main element of true.
Ex:
div has:(p) = div p
Rules apply to the left element “div” not right side element “p”
h1 has:(+ div) = h1 + div
Rules apply to the h1 rather than the div as normal
You used :not for the first time the other day? Dang!
I heard a talk recently where a member of the CSS Working group said they had determined in a series of technical meetings that :has() won’t be able to come until we find a workaround for the cyclical referencing issue adding it creates. which is why such a helpful idea has no current browser support.
Which is something I never fully understood – couldn’t we just let the developers take care of resolving those? After all, it works for other stupid mistakes where we may shoot ourselves in the foot, e. g.
p:hover {transform: scale(0.9)}
may result in some jittery business along the edges, but that is for us devs to take care of.Adblock Plus filters have support for a has pseudo-selector. Theirs is
-abp-has
. I found it useful when selecting a div with generated id and classes, but with a unique child dom stucture. https://adblockplus.org/filter-cheatsheet#elementhideemulationIt would be nice for something like a label containing a checkbox. :has(:checked). Or div.form-control:has(input:invalid)
I’m sure there’s a lot more usecases
Does :has() need to apply to a direct child, or can it be more of a contains?
Such as: body:has(.printArea) {
// Hide all but printArea
}
That way, If a content creator defines a printArea class, it’s used, but if not, the site default @media print preference is used.
Hmm.. :not() is really, really old. It has been available in Firefox since 2006 and safari since 2008.
This selector is just too useful and I have been looking forward to its implementation for a long time.
Hi, it’s nice article but I couldn’t understand what are special operators need to use example +, – and > you have used. What are those meanings and what else operators are available?
These are regular CSS selector combinators. You can find them all over at MDN and read more about them at CSS tricks :-)
has()
,not()
,is
,.I think the
has()
should be use like thisdiv has(.show){ display: block }
I’ll expect
where()
,if()
,with()
Support for this as a CSS selector would be fantastic. I found it quite useful back in the day when I used jQuery a lot (see https://api.jquery.com/has-selector/).
The :has pseudo selector has been in the wishlist for years, perhaps even more than a decade… but browser vendors have repeated again and again that it’s impossible to implement. Until we hear noises to the contrary, I wouldn’t waste my time with it
Selector :not() was defined in CSS3 but it could contain only one selector. Still it’s very useful if you know what you are doing (e.g. div:not(.hidden) { display: block }) or need to shorten some more advanced selectors (e.g. li:not(:first-of-type):not(:last-of-type) { … } )
I eagerly await the adoption of :has(). Imagine:
Currently, the only way to check for this is to use JS. A clean way to do it in CSS would be lovely!
Plenty of other fantastic possibilities too!
Do we know if it will nest?
div:has(a:has(img))
???Ick… that’s not great for readability though.
yes dude
Hi, I have a question.
We have parent element A
and within it, we have child element B
<A>
<B></B>
</A>
<A>
<B></B>
</A>
<A>
<B></B>
</A>
Each element B gets a class in which its background color is for example, so the code looks like this :
<A>
<B class="red"></B>
</A>
<A>
<B class="green"></B>
</A>
<A>
<B class="blue"></B>
</A>
My question is, is it possible to set the background color of parent element A in relation to the background color of its child element B ???
TNX :)
It will be possible when
:has()
is supported, yeah.This could be used as alternative to
:*-within
pseudo-classes.No?
Everything is OK but the stones did not fit properly :)
What is the difference with two things:
div:has(p) {color: #000}
div p {color: #000}
Why should I use :has() selector?
div:has(p) – “color” is used for all content of this “div” – but only if this “div” contains “p” tag.
div p – “color” is used only in “p” tags in “div”.
Is it also possible to combine a CSS selector with an CSS operator?
I’m trying to highlight a table row where one of the cells contains
So my:
tbody.plugins tr td.plugin-title p img[src*=”pluginname”] { background-color: var(–postpone-update-alert) !important; }
Needs to become something like:
tbody.plugins tr:has(td.plugin-title p img[src*=”pluginname”]) { background-color: var(–postpone-update-alert) !important; }
as I want to apply the highlighting to the entire row but that doesn’t work…
Is it possible at all? Or am I just overlooking something?
What does
article:has(h2, ul)
mean?