# Feature detecting :has()
To feature detect browser support for the CSS :has()
selector, you can use @supports(selector(…))
. When doing so, it is important to include a valid selector as its argument. As I’ve tweeted before, you must pass a selector such as *
into :has()
when used in a feature query.
/* ❌ This will always evaluate to false */
@supports selector(:has()) {
…
}
/* ✅ This will evaluate to true in browsers that support :has() */
@supports selector(:has(*)) {
…
}
💁♂️ Initially this selector requirement was only the case in Safari, but this has since been adjusted at the spec level making it a requirement in all other browsers.
I use this technique to conditionally show a warning in many of my :has()
demos.
/* Style warning block */
.no-support {
margin: 1em 0;
padding: 1em;
border: 1px solid #ccc;
background-color: #ff00002b;
display: block;
}
/* Hide warning block in case :has() support is detected */
@supports selector(:has(*)) {
.no-support {
display: none;
}
}
If your browser has no support, you get to see a warning message telling you about it. This is the case in Firefox which, at the time of writing, does not support :has()
out of the box just yet.

:has()
demos. As it lacks support for :has()
, visitors get to see a warning message telling them the demo will not work correctly.~
# The Problem with :has(*)
While the approach above does allow you to feature detect :has()
, it is not 100% closing. The culprit here is Firefox, which currently has experimental support behind a feature flag.
When flipping the layout.css.has-selector.enabled
flag on, Firefox will correctly claim support when using @supports selector(:has(*))
. This, however, does not account for the fact that this experimental implementation does not support relative selector parsing (yet).
:has()
so regular feature detection works just fine now. However, do note that the :has()
implementation in Firefox is not finalized yet, as style invalidation still needs to be tackled. See this comment for some more info.As many of my demos use relative selectors within :has()
, I do want a warning to be shown even when the flag is flipped on. The solution here is to actually use a relative selector such as + *
as the argument to :has()
.
Like so:
/* Hide warning block in case :has() support – including relative selectors – is detected */
@supports selector(:has(+ *)) {
.no-support {
display: none;
}
}
~
# Full Code
The full code used in my demo looks like this. It can detect both levels of support and also allows showing a certain box in case support is claimed.
<div class="no-support" data-support="css-has-basic"><p>🚨 Your browser does not support CSS <code>:has()</code>, so this demo will not work correctly.</p></div>
<div class="no-support" data-support="css-has-relative"><p>🚨 Your browser does not support relative selectors in CSS <code>:has()</code>, so this demo will not work correctly.</p></div>
.no-support,
.has-support {
margin: 1em 0;
padding: 1em;
border: 1px solid #ccc;
}
.no-support {
background-color: #ff00002b;
display: block;
}
.has-support {
background-color: #00ff002b;
display: none;
}
@supports selector(:has(*)) {
.no-support[data-support="css-has-basic"] {
display: none;
}
.has-support[data-support="css-has-basic"] {
display: block;
}
}
@supports selector(:has(+ *)) {
.no-support[data-support="css-has-relative"] {
display: none;
}
.has-support[data-support="css-has-relative"] {
display: block;
}
}
~
# TL;DR
If you’re feature detecting :has()
with @supports
you must pass a selector into :has()
. This can be *
but if your code relies on relative selectors used inside :has()
, use @supports selector(:has(+ *))
instead. This must be done to filter out Firefox visitors who have flipped on the experimental :has()
support which currently lacks support for relative selectors.
~
# Spread the word
To help spread the contents of this post, feel free to retweet its announcement tweet:
CSS :has() feature detection with @supports(selector(…)): You want :has(+ *), not :has(*)
🏷 #css #FeatureDetection #selectors pic.twitter.com/yLf44AJy8a
— Bram.us (@bramusblog) January 4, 2023
~
🔥 Like what you see? Want to stay in the loop? Here's how:
Thanks for helping us keep on top of things, as usual, Bramus!
Looks like this is throwing a false positive in Firefox 112, CodePen for testing: https://codepen.io/5t3ph/pen/qBJPjad
If you come up with the right combo, let me know!
Technically it’s not a false positive as Firefox does support relative selector matching by now – see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1774588, which is closed/fixed.
However, what’s still an issue is style invalidation. Updates to the DOM or CSS don’t visually update things matched by `:has()` selectors with relative selectors.
See my demo at https://cdpn.io/pen/debug/YzvowwJ for example. It kinda works, but you often need to force a style recalc by toggling the `.special` class off and on again – `$0.classList.toggle(‘special’); $0.offsetLeft; $0.classList.toggle(‘special’); `