Highlight table columns with CSS :has()

While working with large tables of data, it can often be difficult to scan through all the rows and columns. Without visual styles, pinpointing information at the crossroads of a specific row and column is challenging. One way to assist sighted people in parsing your data is to highlight both the row and column they hover over or tap on, creating a crosshair at that table cell. While this has been possible using JavaScript or massive pseudo elements, CSS's new :has() pseudo class removes the need for scripting or clever CSS.

Note that this pseudo class isn't available everywhere yet. Be sure to check the browser compatibility.

The Setup

First, let's look at how to colour the rows. Generally, I style table rows with alternating background colours to visually distinguish them from one another. This can be accomplished using :nth-child pseudo elements. For example, tr:nth-child(even) { background: #eee; } will colour every second row light grey.

Striping is also possible with columns using the same method: tr > *:nth-child(even) { background: #eee; }. The * allows selecting th elements in the table head as well.

However, striping both rows and columns creates a checkerboard look which is more visually distracting than helpful. We want this to happen only on hover. Rows are straight-forward: tr:hover { background: #ccc; } When it comes to columns though, there's no corresponding column element to use (the col element is a grouping element and not visually drawn on the page, so can't be hovered over or tapped on).

Finally, the :has() Pseudo Class

This is where :has() comes in. We want to colour every table cell in each row that's in the same column as the cell we're hovering over. The cousin elements, as it were. Here's how to accomplish that: table:has(tr > *:nth-child(1):hover) tr > *:nth-child(1)

This selector reads "Select the first (direct) child element of all rows that are within a table that has an element being hovered over which is the first direct child of a row."

You'll notice we're specifying the the :nth-child. Unfortunately, to target the correct column, there isn't away around that. A selector for each column is needed. CSS custom properties (variables) can't be used in selectors. However, using a pre-processor like SCSS, a variable and loop can be used to streamline the code.

%column-styles { // Using a placeholder means the styles are reused, not duplicated
    background: #ccc3;
}
@for $i from 1 to 11 { // Total number (or more) columns in the table
    table:has(tr > *:nth-child(#{$i}):hover) tr > *:nth-child(#{$i}) {
        @extend %column-styles;
    }
}

Here's a pen of it all working together (make sure you're using a compatible browser like Safari).

See the Pen Column highlighting on hover with CSS :has() by Philip Renich (@philiprenich) on CodePen.

Further Possibilities

There's a lot more that :has() can do. I encourage you to start reading up on it. It won't be long before it's available in more browsers. Of course, be careful about putting critical styles in a feature that is only partially supported.

The inspiration for this post came from watching Eric Meyer's video on :has(). It's a great place to start learning more about the pseudo class. In the video he touches on some of what's possible, using real-world examples.

Got a good example or idea of what :has() can be used for? Tell me on Twitter!