Is it possible to select p if it has a neighbour div with a inside?
You would need to use the :has()
selector, however the full solution depends if you want to use it in a "live profile" or a "snapshot profile":
The live profile is appropriate for use in any context, including browser CSS selector matching, which is live. It includes every selector defined in this document, except for :has()
:
The snapshot profile is appropriate for contexts which aren’t extremely performance sensitive; in particular, it’s appropriate for contexts which evaluate selectors against a static document tree. For example, the querySelector()
method defined in DOM should use the snapshot profile.
When talking about :has()
specifically:
- In
.css
/<style>
stylesheet rule selectors (which is a live context): No.
- In
.css
/<style>
stylesheet rule selectors with client-side scripting to allow the use of :has()
(which means it actually is used a snapshot context): Yes
- In
querySelector
, querySelectorAll
, matches
and other DOM functions (which is a snapshot context): Yes
- But as of May 2020 no browser natively supports using
:has()
in these functions.
- And after some cursory googling, I couldn't find a polyfill or patch to add support for
:has()
for querySelector
etc, boo!
But assuming you can use :has()
(e.g. because you're reading this post in the year 2030), then this query is all you need:
div:has(> a[href="/foo"]) ~ p
Why isn't :has()
in the Live Profile?
There are a variety of reasons why CSS does not support selecting elements based on its children (though there are a limited number of exceptions such as :empty
and :focus-within
), but the main reasons are:
- It would make CSS selectors even more computationally expensive to evaluate.
- CSS is designed to work with a partially loaded document, such that as a document is loaded it won't drastically change appearance.
As a thought-experiment: pretending that browsers did support :has()
in a CSS style rule, consider a HTML document that looks like this:
<html>
<head>
<style>
p { background-color: red; }
p:has(div) { background-color: blue; }
</style>
</head>
<body>
<p>foo <div></div></p>
<p>bar</p>
<p>baz <div></div></p>
</body>
</html>
The "foo" and "baz" elements would be blue, and the "bar" element would be red.
Now if a browser has this document in a partially-loaded state, such that it only has this loaded so far (before the user's Wi-Fi connection died) - or before the next TCP packet arrives:
<html>
<head>
<style>
p { background-color: red; }
p:has(div) { background-color: blue; }
</style>
</head>
<body>
<p>foo
Browsers are required to cleanly terminate a partially-loaded document by inserting their own closing-tags for truncated elements, like so:
<html>
<head>
<style>
p { background-color: red; }
p:has(div) { background-color: blue; }
</style>
</head>
<body>
<p>foo</p>
</body>
</html>
So when the document is in this state, the first p
would be red, not blue - then after the user's Wi-Fi connection reconnects (or the next TCP packet arrives) and the page finishes downloading then <p>foo</p>
gains the <div>
and so the :has(div)
rule now applies and the <p>foo</p>
will suddenly change from red to blue - which isn't good.
Now in this contrived example, a trivial difference like a background-color change doesn't matter all that much - but if the :has()
selector was used to control some major differences in page layout (e.g. body:has(footer) > main { display: grid;
) then that would really mess-up the page layout while it's still loading - and this is why selectors based on descendant content will not be supported for now in their current form.
(But given the sheer practicality and utility of parent-of-descendant selectors, I imagine they'll be reintroduced in the future with some way of restricting rules for use only after DOMContentLoaded
has fired or requiring a default or fallback for when a selector is indeterminate.