Highlighting Selected Table Rows with CSS and the :has() Selector
Let’s say you’re displaying a collection of items, like a list of blog posts or users, in some sort of admin interface. You’ll probably want to let users select multiple items in order to perform batch operations, like deleting multiple blog posts or assigning multiple users to a group. This is a very common pattern, and is often accomplished using a <table> and checkboxes:
<table>
<thead>
<tr>
<th>
<input type="checkbox" name="selectAllRows" value="1" class="selectAllRows">
</td>
<th>
Name
</th>
<th>
Email
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input type="checkbox" name="selectRow[]" value="1" class="selectRow">
</td>
<td>
John Smith
</td>
<td>
[email protected]
</td>
</tr>
<tr>
<td>
<input type="checkbox" name="selectRow[]" value="2" class="selectRow">
</td>
<td>
Sarah Jones
</td>
<td>
[email protected]
</td>
</tr>
<tr>
<td>
<input type="checkbox" name="selectRow[]" value="3" class="selectRow">
</td>
<td>
Jane Williams
</td>
<td>
[email protected]
</td>
</tr>
</tbody>
</table>It’s also very common to provide some helpful feedback by highlighting user-selected rows. Such highlighting is usually done via JavaScript. However, it’s now possible with some CSS, and specifically, the :has() selector. As CSS Tricks explains:
The CSS
:hasselector helps you select elements that contain elements that match the selector you pass into the:has()function. It’s essentially a “parent” selector, although far more useful than just that.
Using the above <table> example, the :has() selector allows us to highlight individual rows by checking to see if a <tr> contains a checked checkbox. If it does, then a background color is applied to its children <td> elements to indicate that it’s been selected.
This is all it takes:
tr:has(.selectRow:checked) > td {
background-color: #ccc;
}Note that I’m using the child combinator — > — just in case a <td> contains a <table>. This will prevent any nested <table>’s <td> elements from being highlighted, which could look weird. I could also simplify the CSS by replacing the .selectRow selector with [type=checkbox], but I’m using a class to be more specific in case any rows contain multiple checkboxes.
Here’s what it looks like in action:
See the Pen Untitled by Jason Morehead (@jasonopus) on CodePen.
As cool as this is, there is — surprise! — one major caveat.
Specifically, this only works for highlighting individual rows. If you want to highlight all of the <table> rows at once with a “Select all rows” checkbox, then you’ll still need to use JavaScript. While you could use CSS to apply the “all rows selected” styling, you can’t (un)check a checkbox with CSS. The above CodePen includes some very simple JavaScript for (de)selecting all rows, but there are many different ways to do that.
And this isn’t so much a caveat, but the above HTML is very stripped down. Ideally, you should also add hidden labels for the checkboxes (or something similar) to ensure their accessibility:
<td>
<input type="checkbox" id="selectRow1" name="selectRow[]" value="1" class="selectRow">
<label for="selectRow1" class="visually-hidden">Select row 1</label>
</td>This is a really simple yet great example of what’s now possible with CSS. Sometimes, it’s easy to forget just how robust and comprehensive CSS has become in recent years. Features like flexbox, grid, CSS nesting, and now, the :has() and :is() selectors, allow us to craft web experiences in ways that would’ve been impossible even just a few years ago without resorting to jQuery plugins or external tools.
Now, if one were inclined to be pedantic — which is often the case with us developers — one could argue that this unnecessarily blurs the line between CSS and JavaScript. Traditionally, JavaScript has been about controlling and enhancing how elements behave. Depending on your view, handling checkbox interactivity could fall into the “behavior” category and thus, should be handled by JavaScript.
I’m not sure I agree with that; my preference is to follow the “Rule of Least Power” and do as much as possible with CSS before turning to JavaScript. In any case, this still shows just how mature CSS has become and, in the process, challenge preconceived notions of how it can — and should — be used.