Zebra tables using nth-child and hidden rows?

Thursday, December 12th, 2013 at 3:39 am

lion dressed as zebra

Earlier today my colleague Anton Kovalyov who works on the Firefox JavaScript Profiler ran across an interesting problem: if you have a table in the page and you want to colour every odd row differently to make it easier to read (christened as “Zebra Tables” by David F. Miller on Alistapart in 2004) the great thing is that we have support for :nth-child in browsers these days. Without having to resort to JavaScript, you can stripe a table like this.

Now, if you add a class of “hidden” to a row (or set its CSS property of display to none), you visually hide the element, but you don’t remove it from the document. Thus, the striping is broken. Try it out by clicking the “remove row 3” button:

The solution Anton found other people to use is a repeating gradient background instead:

This works to a degree but seems just odd with the fixed size of line-height (what if one table row spans more than one line?).

Jens Grochtdreis offered a pure CSS solution that on the first glance seems to do the job using a mixture of nth-of-type, nth-child and the tilde selector.

This works, until you remove more than one row. Try it by clicking the button again. :(

In essence, the issue we are facing here is that hiding something doesn’t remove it from the DOM which can mess with many things – assistive technology, search engines and, yes, CSS counters.

The solution to the problem is not to hide the parts you want to get rid of but really, physically remove them from the document, thus forcing the browser to re-stripe the table. Stuart Langridge offered a clever solution to simply move the table rows you want to hide to the end of the table using appendChild() and hide them by storing their index (for later retrieval) in an expando:

[…]rows[i].dataset.idx=i;table.appendChild(rows[i]) and [data-idx] { display:none }

That way the original even/odd pairs will get reshuffled. My solution is similar, except I remove the rows completely and store them in a cache object instead:

This solution also takes advantage of the fact that the result of querySelectorAll is not a live list and thus can be used as a copy of the original table.

As with everything on the web, there are many solutions to the same problem and it is fun to try out different ways. I am sure there is a pure CSS solution. It would also be interesting to see how the different ways perform differently. With a huge dataset like the JS Profiler I’d be tempted to guess that using a full table instead of a table scrolling in a viewport with recycling of table rows might actually be a bottleneck. Time will tell.

Share on Twitter