Filtering Content With the CSS :has() Selector
The CSS :has() selector can do more than style a parent. It can also connect the state of a form control to content elsewhere on the page, which is enough to build a small filter without JavaScript.
Here is a list split into two teams, with one checkbox for each team:
<main>
<div class="filters">
<input type="checkbox" id="a-team-filter" checked>
<label for="a-team-filter">Team A</label>
<input type="checkbox" id="b-team-filter" checked>
<label for="b-team-filter">Team B</label>
</div>
<ol>
<li class="a-team">John</li>
<li class="b-team">Robert</li>
<li class="b-team">Donald</li>
<li class="a-team">Rupert</li>
</ol>
</main>
Let the checkbox state control the list
The useful part is only four lines of CSS:
body:has(#b-team-filter:not(:checked)) .b-team {
display: none;
}
body:has(#a-team-filter:not(:checked)) .a-team {
display: none;
}
Read the first selector from left to right: when the body contains an unchecked #b-team-filter, hide every element with the .b-team class. The second rule does the same for Team A.
This is a nice use of :has() because the checkboxes and results do not need to be adjacent. The common ancestor provides the connection.
There is a trade-off. CSS can change what is visible, but it does not give you the state management, URL parameters, announcements, or more complex matching that a proper JavaScript filter can provide. I would use this for a small optional filter, not for the main search interface on a large site.
You can try the complete CodePen demo.