Limitations of the :has() Selector
Learn about the limitations of the :has() selector.
To better understand what :has()
can do, let’s quickly review the most impactful things it can’t do.
:has()
isn’t a forgiving selector
Unlike the other relational pseudo-class selectors :is()
and :where()
, selectors within :has()
must all be considered valid by the browser for the rule to be applied. This is because :has()
is not a “forgiving selector”. This means that, given :has(:invalid-selector, img, p)
, the whole rule will be thrown out and not applied—even if there is an <img>
or a <p>
present.
:has()
can’t be nested within another :has()
Unfortunately, a selector like :has(p:has(a))
is invalid because :has()
isn’t allowed to be nested within another :has()
.
The good news is that, because of how the relational selector list works, we can write :has(p a)
and achieve the same result—that is, testing whether a paragraph containing an <a>
element exists.
:has()
can’t detect the presence of pseudo-elements
While :has()
can be used to detect pseudo-classes like :checked
, it can’t check for pseudo-elements due to performance implications. The spec notes that, since pseudo-elements only exist conditionally and must be attached to their ancestors, querying for pseudo-elements with :has()
introduces cycles, given how the browser evaluates CSS rules. Examples of pseudo-elements include:
::before
::after
::marker
::selection
Get hands-on with 1400+ tech skills courses.