File this under stuff you don’t need to know just yet, but I think the :has
CSS selector is going to have a big impact on how we write CSS in the future. In fact, if it ever ships in browsers, I think it breaks my mental model for how CSS fundamentally works because it would be the first example of a parent selector in CSS.
Before I explain all that, let’s look at an example:
div:has(p) {
background: red;
}
Although it’s not supported in any browser today, this line of CSS would change the background of a div
only if it has a paragraph within it. So, if there’s a div with no paragraphs in it, then these styles would not apply.
That’s pretty handy and yet exceptionally weird, right? Here’s another example:
div:has(+ div) {
color: blue;
}
This CSS would only apply to any div
that directly has another div
following it.
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 is so utterly strange to me because it breaks with my mental model of how CSS works. This is how I’m used to thinking about CSS:
/* Not valid CSS, just an illustration */
.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.
Putting those concerns aside though, if I just sit down and think about all the ways I might use :has
today then I sort of get a headache. It would open up this pandora’s box of opportunities that have never been possible with CSS alone.
Okay, one last 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.
Although :has
isn’t supported in browsers right now (probably for those performance reasons), it is part of the CSS Selectors Level 4 specification which is the same spec that has the extremely useful :not
pseudo-class. 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:
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;
}
So that’s it! :has
might not be useful today but 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.
Definitely feels like reading a CSS selector with
:has()
will take a while to get used to.Although, really excited about
:where()
!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.
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.