Modern selectors
Pseudo-classes like :hover and :first-child check the state or position of an element. The newer pseudo-classes take other selectors in parentheses and allow what used to be impossible: for example, picking a parent by what lies inside it. All of them work in modern browsers.
:is() — one of a list
:is() matches if the element matches any selector from the list. It cuts repetition:
<style>
:is(h2, h3, h4) { color: steelblue; margin: 4px 0; }
</style>
<h2>Heading h2</h2>
<h3>Heading h3</h3>
<h4>Heading h4</h4>
<p>The paragraph did not change</p>Without :is() you would write h2, h3, h4. The gain shows in long chains: .post :is(h2, h3) a instead of .post h2 a, .post h3 a.
:where() — the same, but weightless
:where() selects the same things as :is(), but its specificity — the strength of a selector in the cascade — is always zero. Any other rule easily overrides it, so :where() is handy for base styles:
<style>
:where(.article) a { color: seagreen; }
a { color: crimson; }
</style>
<p class="article"><a href="#">The link is red</a>: the simple selector a beat the rule with :where().</p>:not() with a list
:not() selects elements that do not match a selector. Several selectors can be listed inside with commas:
<style>
li:not(:first-child, .muted) { color: crimson; }
</style>
<ul>
<li>The first — not red</li>
<li>The second — red</li>
<li class="muted">Muted — not red</li>
<li>The fourth — red</li>
</ul>:has() — selecting by content
:has() selects an element that contains something matching a selector. It is called the "parent selector": CSS used to look only down the tree, and now it can look up as well:
<style>
.card { padding: 12px; margin-bottom: 8px; border: 2px solid silver; }
.card:has(svg) { border-color: gold; }
</style>
<div class="card">A card without a picture</div>
<div class="card">
<svg width="40" height="40"><circle cx="20" cy="20" r="18" fill="gold"/></svg>
A card with a picture
</div>:has() arrived in all major browsers at the end of 2023.
:has() and form state
:has() reacts to changes: a parent can restyle itself when a checkbox inside is ticked. Click the checkbox:
<style>
.option { padding: 12px; border: 2px solid silver; }
.option:has(input:checked) { border-color: seagreen; background: honeydew; }
</style>
<label class="option">
<input type="checkbox"> Courier delivery
</label>This effect used to require JavaScript.
Common mistake
The specificity of :is() equals the strongest selector in its list. If an id gets into the list, the rule becomes very heavy and beats classes, even when it stands above them:
<style>
:is(#intro, p) { color: crimson; }
.note { color: seagreen; }
</style>
<p class="note">We wanted green, but got red</p>Here the paragraph is selected through p, but the whole :is() got the weight of an id. Do not mix ids and simple selectors in one :is(), and use :where() for light rules.