Zebra Tables

A note from the editors: While useful in its day, the JavaScript and PHP techniques discussed here have been made obsolete (as anticipated by the article itself) by the CSS nth-child pseudo-selectors.

Ever since CSS hit the big time, the table has become increasingly rare. Semantic markup and CSS have replaced tables as layout tools. Tables are now relegated to their original role: displaying data stored in records (rows) and fields (columns).

Article Continues Below

However, their new status doesn’t mean that they still can’t be the targets of a designer’s styles and a developer’s hacks. A table typically presents more information than the rest of the page in a much smaller area, and much effort has been spent in attempting to make tables and other data visualizations as easy to interpret as possible.

The web designer’s and developer’s toolbox of the DOM, CSS, and JavaScript can aid in this effort. This article will take a look at how a simple method of altering a table’s appearance can alter the ease with which its information is interpreted, and along the way, we’ll take a look at how it is possible to mimic some of CSS3’s power through some old-fashioned JavaScript and the DOM.

Zebras and tables and cells, oh my#section2

Take a peek at the following screen-capture (anyone who has used iTunes will instantly recognize its origin):


The stripes, alternating between white and light blue, serve two purposes:

  • They’re aesthetically pleasing.
  • They guide the reader’s eye, allowing the reader to piece together the bits of information that belong to a particular song.

While the latter reason may not be noticable in the above image, take a look at these images to gain an idea of how useful the stripes become when the gap between the two columns is much larger. In the event that you employ a liquid layout for a site, you may end up with the situation shown in the above comparison: columns are separated by large amounts of whitespace, making it difficult to jump from one column to the next. Let’s see if we can’t remedy the problem.

Solution #1: the usual suspect#section3

Re-creating the stripe effect in an XHTML table is pretty easy: we simply alternate background colors for the cells in the even and odd rows. This can be easily achieved by creating two CSS classes (one for even rows, the other for odd) and then binding them to the appropriate rows. A simple example of this method follows:

<html>

<head>

	<title>tables</title>
	
	<style type="text/css">
		#playlist tbody tr.even td {
			background-color: #eee;
		}
		#playlist tbody tr.odd  td {
			background-color: #fff;
		}
	</style>
	
</head>

<body>

	<table id="playlist">
		<tbody>
			<tr class="odd">
				<!-- cells go here -->
			</tr>
			<tr class="even">
				<!-- cells go here -->
			</tr>
			<tr class="odd"> 
				<!-- cells go here -->
			</tr>
			<tr class="even">
				<!-- cells go here -->
			</tr>
		</tbody>
	</table>
	
</body>

</html>

Because there are only two possible holes for this pigeon (even/odd), we can get away with applying only one class, and assume that rows without a class declaration must fall into the the other hole:

<html>

<head>

	<title>tables</title>
	<style type="text/css">
	#playlist tbody tr td {
	background-color: #eee;
	}
	#playlist tbody tr.odd  td {
	background-color: #fff;
	}
	</style>

</head>

<body>

<table id="playlist">
	<tbody>
		<tr class="odd">
			<!-- cells go here -->
		</tr>
		<tr>
			<!-- cells go here -->
		</tr>
		<tr class="odd">
			<!-- cells go here -->
		</tr>
		<tr>
			<!-- cells go here -->
		</tr>
	</tbody>
</table>

</body>

</html>

Pretty simple, no? After compacting the white space in the second example, we’ll have saved our visitors and server even more bytes in precious bandwidth. However, we still run into the same inherent problems as our first example; one may cause you to pull out your hair, while the other is relatively trivial to work around. Let’s take a look-see.

Problem #1: manual labor#section4

The purpose of stylesheets, whether it be on the web via CSS or in an application such as Adobe InDesign is to control the visual properties of elements contained within a document. And while CSS’s selectors allow you to specify which document elements should have which styles applied, the above solution requires the author to apply the appropriate class (either even or odd) to atleast half of the rows in a table. While this isn’t a problem if the table contains only a few rows, copying and pasting gets old very quickly when the table contains tens or hundreds of rows.

This problem can be avoided when the table is generated on the server side, since modifications to the table’s markup require only the editing of a few lines of code. But not every web page is built by a server-side application, and even if you are creating a web app, it is frequently desirable to off-load layout processing to the client through CSS and JavaScript.

Problem #2: row consistency#section5

If you move rows around within the table while authoring the page, chances are that you’ll end up with a sequence of two or more rows with odd or even classes following each other, which will cause your nice stripes to be transformed into a mess of random lines. Correcting the attributes can prove to be tedious when there are many rows in the table.

So what���s a web developer to do? Eventually CSS3’s pseudo-selectors will resolve this problem;  however, browser support for CSS3 is currently sketchy at best.

Solution #2 : open your toolbox#section6

However, there is another solution to our problem. The odd/even method mentioned above uses pure CSS; it’s time to put away the hammer and try the other tools in our belt: JavaScript and the DOM.

Instead of manually binding CSS classes to the appropriate rows, we’ll use a pinch of JavaScript to walk through the table and apply CSS styles. Let’s take a look at the recipe:

  1. Obtain a reference to the table that we want to add stripes to.
  2. With that in hand, drill down into the table’s child elements to find all the <tr>s that are contained within <tbody> elements.
  3. Loop through the <td>s and apply the appropriate styling (which is determined by the location of this <tr> in the table).
  4. Season to taste.

While it sounds pretty simple in theory, it is almost as easy in practice. Honest. The following is our tasty morsel that accomplishes exactly what we need (and scores bonus points for being documented). View the script.

Let’s see how it turned out.

This method comes with two advantages:

  • We can now use classes for more specific uses in the table (using the iTunes metaphor, for example, we can indicate whether the track is enabled or disabled, or if it’s currently selected).
  • We are now able to automatically add stripes to tables in any page that has the stripe() function included; we simply need to invoke the function with the table’s id as an argument at some point in the page.

And while our tasty morsel does require a bit of preparation when compared to our first solution, the code is straightforward and easy to customize. And once it’s in your toolbox, you’ll never have to look at it again.

About the Author

David F. Miller

David F. Miller has been dabbling with code, nodes, attributes, and elements since graduating from the University of Calgary in 2002. His personal site--three years out of date--should one day catch up to the work he's been doing in the meantime.

97 Reader Comments

  1. Here we go
    <script type=”text/javascript”>
    //Powered by DCScript
    function zebra(tID,ecolor,ocolor) {
    var node = document.getElementById(tID);
    var tds = node.firstChild.firstChild.childNodes;
    for (var i=0;i
    The first parameter is the table ID, then the color for evens, and then the colors for odd. Call it like
    <body onLoad=”zebra(‘data’,’blue’,’red’)”>

  2. I’ve been struggling with this for a long time: why can’t I use CSS to make information display in the same tabular way that tables do? It frustrates the heck out of me that W3C still hasn’t come up with a solution to this. I can make it look nice w/ 2 columns or 3, but any more than that and I’m hosed. As Z says: “Tables are dead . . . Long Live Tables”

    (ps – anyone who knows how to get past this is welcome to email me! mike@smallBLOC.com)

    but a great article all the same (i just tend to rant a little)

  3. If you are a designer and can’t write a simple function like this, you are going to be out of a job soon…let’s take a php example:

    <?php
    function getClass($Num)
    {
    if (($Num % 2) == 0)
    {
    return “Reg”;
    }
    else
    {
    return “Alt”;
    }
    }
    ?>

    Then on your table:

    <table>
    <tr class=”HeadRow”>
    <td>Blah</td>
    <td>Foo</td>
    <td>Bar</td>
    </tr>
    <?php
    foreach($DBStuff as $Stuff)
    {
    $RowClass = getClass($i);
    print(“<tr class=”” . $RowClass . “”>n”);
    print(“<td>” . $Stuff->Blah . “</td>n”);
    print(“<td>” . $Stuff->Foo . “</td>n”);
    print(“<td>” . $Stuff->Bar . “</td>n”);
    print(“</tr>n”);
    $i++;
    }
    ?>
    </table>

    Then in your stylesheet, tell what tr.Reg and tr.Alt are supposed to look like:

    tr.Reg
    {
    background-color: #FFF;
    }
    tr.Alt
    {
    background-color: #E6E6E6;
    }

    If designers don’t start learning simple server side languages they won’t survive in the future market…

  4. In the case where you retrieve the table data from a database, a server-side language is obviously the better solution. But what about the case where you enter the data manually?

  5. Nice approach – I used to use PHP and a mod division to see which row was even or odd but the drawbacks to that approach are obvious.

    Great tip!

  6. Am considering using this method for an html table builder plugin within a inhouse content management system, but one of the condieration is that it works in i.e 5.5. The javascript listed does not support this method but by removing node from obj.getAttributeNode within the hasClass function works like a dream! Also tested on i.e 6 and netscape 7+.

  7. If I wanted the even or odd row color background to be dark and I wanted the text in that row to be white, how would I accomplish this?

  8. Rob – if you want to change the text color in a table row/cell, then just specify that in your class, like so:

    .oddclass {
    background: #ffc;
    color: navy;
    }

  9. Just plug this code into the head, and call it in the body tag, and it will “zebra” any table on any HTML page:

    <script type=”text/javascript”>
    function tableChange()

    {
    var rows = window.document.getElementsByTagName(‘tr’);
    for(var i = 0; i < rows.length; i++)
    {
    (i%2==0)? rows.item( i ).style.backgroundColor = “lightblue” : rows.item( i ).style.backgroundColor = “E0EFEE”

    } }
    </script>

    Of course, this is just for alternating 2 colors. But no other code is needed. No class designations, no stylesheet, no nothing.

  10. Don’t use item( i ) in Javascript. Instead of rows.item( i ) use rows[ i ]. It’s a lot easier. Item is useless.

    But what if you have another table that you don’t want striped?

  11. Q. How do I apply this script to multiple tables in the same page? I tried using the same id for each, but am missing something.

    d.

  12. don – I’ve just implemented the short version of this script into my new corporate site. The site uses nested tables for layout – so obviously applying the zebra function to all tables would be right out.

    I have pages with big lists of forms on them and was able to use this method to zebra them even on pages with several different list tables simply by removing the #playlist {border: 1px solid #666666;} and assigning the border to another class (which I called .stripedtable then putting a div around ALL the tables I want striped and assigning the id to the div.

    I changed the css so that the various rules applied to only those tables of class stripedtable and not to the others.

    .stripedtable {border: 1px solid #666;}

    #coformlist table.stripedtable tbody tr.even td {background-color: #efefef;}

    it works a treat

  13. I’ve seen it happening around, people trying to make a list look/behave like a table. Can anyone please explain to me why this is being done? I mean, a table is a table after all. A list should (in my opinion) be a list (it doesn’t have to look like an ordinary list though).
    Is it that people think that tables are bad now? They not bad if used properly, this means used to display tabular data (I agree they’re bad when used for layout).

    For example, if I look at http://www.tbrown.org/ideas/tabularlist/ with CSS turned off, I get a few lists down in a row, this doesn’t give me the overview I get when looking at a table.
    I mean it’s nice just as an experiment, and I really think he has done a good job, but for displaying tabular data it looks no good to me (with css turned off).

    I thought that was the whole point of this new wave going on in webdesignerworld, use tags for what they are meant for. Sure you can style a div so that it would look like a regular blockquote, but we already have a tag for that, if you want it to look different, use CSS. Sure you can layout a website using tables, but that’s not what they’re meant for, besides they are other ways to accomplish that. I think most of the readers here at ALA will agree with me on that. But why do we want to make lists look like tables then?

    So can someone please explain what’s going on, am I missing something?
    Thanks alot,
    Jop

  14. When I used the javascript in my code, I tested it in my browser (Safari) and it was beautiful. However, when testing it in a PC environment and in IE for Mac, I noticed that in tables consisting of a lot of rows (100 or more) the alternating colors stop rendering. Is there a work around for this bug?

  15. I’ve managed this one pretty well in my second day of exploring CSS and the likes and got this one up and running pretty quick.

    However, after getting the hang of it, i tried to create a multi-table CSS row on the right site of my page and found out a pretty bizar problem which i can’t seem to solve:

    The first table is drawn ok as it should, but the second one only fills in the color for the class=”selected”.

    I’ve been trying to extend the css and different namings … no luck …

    I think it’s a problem of either import of the CSS (1 declaration of tr/td possible) or something is amis in the javascript.

    I also tried it with 2x the table code on the side and got the same result.

    Any help appreciated 🙂

  16. That’s the longest way I’ve ever seen of alternating rows in PHP 🙂

    $hl = false;
    while (…) {
    // code and markup leading to <tr …
    echo (($hl = !$hl)) ? “#fff” : “#eee”;
    }

  17. This is because the original script does accepts an ID, and as you might know, ID’s (opposed to classes) must be unique, which means you can only stripe one table per function call.
    So either you run the function with each seperate ID, or you modify the script to accept classes and iterate through all the items with that class.

    I’ve written a moduralized version of the script, which iterates through all the tables in a page (those without classes that is).
    http://validweb.nl/zebra.txt

    but be sure to look around in the comments too, since other people have written excellent modifications as well which might suit you better!

  18. Hey Jop,

    Thanks for the kind words!

    First of all, I’m with you: use tags semantically. But what if, semantically, unordered/ordered list groupings made the most sense … yet establishing relationships between separate lists via a visual layout (tables, in the case of my example) could enrich a user’s understanding of the data/content in question?

    I guess the first issue would be to justify (on a semantic level, not a visual level) the grouping of data into HTML lists rather than HTML tables. The reasons why I’d use HTML lists would be to take advantage of their inherent hierarchical relationships and the extensibility they provide.

    Both ordered and unordered list items belong to their parent lists, and content within a list item belongs to that list item, including entire other lists. And while this relationship isn’t as pure as the nodal relationship of something like XML, it’s only one step removed, and far easier to practically apply (and justify) than a nested table hierarchy.

    If the use of HTML lists is semantically justified, then the next step would be to decide how best to present the data – taking into consideration the technical constraints and realities of web authoring. To support the formatting of lists as tabular rows or columns, I would cite the ability to juxtapose individual attributes of nested data, provided that corresponding individual attributes align when the nested data is presented visually.

    Neither lists nor tables are the “right” markup for presenting information online as a table; they both have advantages and disadvantages. I suppose we’d have to look to our friends in library science for a perfect solution, but I don’t know much about that, so I’ll let someone else pick up the conversation at this point if they’re interested.

    And conversation is really all I wanted out of my whole experiment in the first place. Well, that and more options for structuring and displaying data.

    Thanks, Jop, for getting me thinking.

    And by the way, Mr. Miller – identifying odd rows with scripting to free up class attributes is an *awesome* idea. Thanks for the great article!

  19. You’re absolutely right about using lists instead of tables. I mean how should the result, the thing that everyone looks at, matter, instead of the code, the thing that only you look.

    In case you can’t tell I’m being sarcastic. I don’t have anything against lists, but they’re getting annoying when they’re overused. Use Tables for tabular data; that way you have backwards compabibility.

  20. One other problem with IE is that it doesn’t handle tbody’s the way it should (i.e. allowing scroll bars just on the tbody {overflow:scroll} like REAL browsers do!). So, to enhance readability on long tables where the column headings scroll off of the page, I insert additional heading rows using tr>th’s often enough to insure there is at least 1 column header row on the screen at any time.

    I modified eelco’s .zebra class code to the following:

    tr td {
    background: expression(this.parentElement.rowIndex%2 ==
    1?’#ffdd99′:’#ffcc66′);
    }

    Since I only had one table on the page (or I might want all of the tables to stripe), I applied it universally in that page. One could use eelco’s original .zebra class to apply it discreetly, if one wanted to. And because I used th’s on the inserted column header rows, it ignores those rows.

    By the way, the ability to put a javascript expression in css to access the DOM is a pretty good idea, and might be a useful addition to css(3?). A decent idea from Microsoft? Who knew!

  21. This is exactly the trick I need to improve my tables on my site. However, I cant get this to work for my pages since they have two tables on each page. JavaScript is loaded, onload is in tag, but nothing happens!

    Any help? For those willing to help me troubleshoot my issues, please email me at s1smith@citicorp.com.

  22. “Has anyone managed to get this to work together with Stuart Langridge’s SortTables? If anyone manages this please let me know.”

    Because of Safari’s (hence OmniWeb’s) broken rows & cells collections, Stuart Langridge’s SortTables won’t work in those browsers — from looking at his code — so construct your own arrays instead:

    // make 2-D array to substitute for rows & cells collections
    function addRowsArray(a_oTBody) {
    var l_aRows = a_oTBody.getElementsByTagName(‘TR’);
    for (var i = 0; i < l_aRows.length; i++) {
    l_aRows[ i ].Cells = new Array;
    for (var j = 0, k = 0; j < l_aRows[ i ].childNodes.length; j++) {
    if (l_aRows[ i ].childNodes[ j ].nodeType == 1) {
    l_aRows[ i ].Cells[k++] = l_aRows[ i ].childNodes[ j ];
    }
    }
    }
    a_oTBody.Rows = l_aRows;
    return a_oTBody;
    }

    As for re-striping the table, immediately after sorting apply something like:

    var i = 0;
    do {
    TBody.Rows[ i ].className = (i % 2 ? ‘oddClass’ : ‘evenClass’);
    // or
    TBody.Rows[ i ].style.background = (i % 2 ? ‘oddColor’ : ‘evenColor’));
    } while (TBody.Rows[++i]);

    (NOTE: Apple fixed the rows/cells bugs a few weeks ago in release 1.2; OmniWeb still manifests them.)


  23. There is a something similar at BrainJar: http://brainjar.com/dhtml/tablesort/.

    Brainjar’s (otherwise brilliant) code has the same weakness. Do not assume native collections work simply because they exist.

    If you make your own arrays by coursing through the nodes of the tables, you know what to expect.

  24. What about this:

    <table ocolor=”#fff” ecolor=”#eee”>
    With the script you could do stuff like:
    var x = document.getElementsByTagName(“table”);
    for (var i=0;i

  25. Wouldn’t it be useful to have CSS lists instead of tables when bringing content from another program, say a word document w/ tabs, then you could copy/paste only rows instead of each cell? Or?

  26. Anyone know how to do this? I want zebra tables, but need a row to change to a certain other color when the mouse hovers over it. This needs to work in IE… 🙁

  27. And take the call out of the body tag.

    <script type=”text/javascript”>
    onload = function() { tableChange () };

    function tableChange()
    {
    var rows = window.document.getElementsByTagName(‘tr’);
    for(var i = 0; i < rows.length; i++) { (i%2==0)? rows.item( i ).style.backgroundColor = "lightblue" : rows.item( i ).style.backgroundColor = "E0EFEE" } } </script>

  28. I’ve made a function to create rows
    but the zebra patern is not quite working.
    Can someone help me out here?

    function crtrow($tabel, $text) {
    if (!empty($tabel)) {
    if ($class == “even”) $class=”odd”; else $class=”even”;
    echo(“

    $text $tabel

    “);
    }
    }

  29. When I use the same id more than once my code doesn’t validate for xhtml. Is there a way to use this javascript by using class rather than id tags? (I’m a beginner)

  30. Ian, as the script stands you cannot. However, you may give each table its own id, then call each in succession:
    |
    onload=”stripe(‘id1’, ‘#fff’, ‘#eee’);stripe(‘id2’, ‘#fff’, ‘#eee’);stripe(‘id3’, ‘#fff’, ‘#eee’);”
    |
    Inelegant, but at least you don’t have to change the script. Good luck!

  31. This helps a lot on people who have a hard time of seeing what info is “all” on the line if there is a blank space between, thanks for the great article.

  32. You know, sometimes I’m surfing all night reading stuff like this in the hope to find something good, and sometimes I do. Like now.
    About the Laptop/Handheld/Palmtop/whatever, it is possible to make a style sheet selector wich will have a high-contrast one in it… of course you could check for the OS and predefine the standard stylesheet, but hey, why not?

    Mathijsken

  33. I use Jop’s alternate script but I just want to apply it to tables with my a specific class an i can’t make out the javascript to do it.
    As it is now it is:

    var tables = document.getElementsByTagName(“table”);

    I guess it should be something like:

    var tables = document.getElementsByClassName(“stripedtable”);

    but it doesn’t work. Anyone knows why?

    There’s also a bug when I’m trying to have another class on a

    like the “selected” class in the original. It works well in IE but in Opera the background of some the row above gets weird. Someone wrote something about the best way to add multiple classes from the javascript.

    As it goes now in Jop’s script it’s:

    if (! hasClass(trs[ i ]) && ! trs[ i ].style.backgroundColor) {
    trs[ i ].className = even ? “even” : “”;

  34. AFter reading this tbrough I believe it is easier to code the alternate bar class as odd then use CSS to set the colors. When changing a table with DW I just use the table command to modify the colors. If I was using a database to populate the table then I would have to use a script (of some kind) but not being a java coder (I use VB script and .asp pages) I find this a pain, as I don’t want to learn another language (having used BASIC and its variants for 40 years.) I use programming tools as adjuncts to teaching medicine.

  35. the tables look cool but when you print them they look like crap. i have designed a solution where but its not well designed as what i do is insert a layer (that has an image that is a color and the size of the layer) underneath every other row or marked row to give it a background color. so when you print the zebra tables it looks as cool on the screen, but i am thinking there is got to be a more classful way to do what i’ve done. anyone interested in seeing this?
    any suggestions?

  36. Hi.

    I see the itunes screenshot that u put here and i can’t remedy my dhtml obsession of clone all i see :P…

    here u have a adaptation of u’r idea to just do that itunes do (i don’t know if the mouseover that i put exists on itunes but it’s there).

    http://www.palaueb.com/javascript/dhtml/itunes.htm

    ah! u only need to write the name of song and the artist, the script itself do the count and attach the checkbox.

  37. Here’s what I came up with:

    // apply alternating classes to lists or tables

    function colorize(itemLoc){
    var fullText = document.getElementById(itemLoc);
    var allLists = fullText.getElementsByTagName(“ul”);
    var allTables = fullText.getElementsByTagName(“table”);

    for (i=0; i

  38. ‘content’ is the id of the element (in most cases, probably a div) inside of which you want to look for items to be zebra-striped. So, for example, on the site I used this for, div#content contains various lists with the class of “stripeList,” various tables with the class of “stripeTable,” and other tables with the class of “checkeredTable” (the last of which is a table with two columns, many rows, to which a style is applied (going left-to-right, row by row) to its first cell, then it’s fourth cell and fifth cell, then its eighth and nineth, and so on and so forth).

  39. This is evil:

    <body onLoad=”function()”>

    Because it does not seperate behaviour and structure. Instead in the JS file put

    window.onload = function;

    Seperating Behaviour and Strucure is very important.

  40. Hey Tim,

    I disagree. I went back and reviewed your lists as tables. The examples you show aren’t semantically correct, and don’t have the correct relationships that a table would.

    First look at it with style sheets turned off. The first ‘list item’ are the column headers. Then the subsequent list items are rows of ‘items’ and thier data, yet thier shown hiearchically the same as the headers. The first ‘list item’ of headers is a completely different type of data than the remaining ‘list items’ and this is reflected nowhere in the code.

    Only a table can show the correct relationship by designating header data in a THEAD, and the body data in a TBODY. Tables also have the capability of linking individual cells with thier appropriate header, something the list example cannot do. The columns can be given a ‘title’ attirbute to add further information about the column, and because of the relationships, that title data is linked to each cell underneath it further describing the content.

    Third and final point is cells don’t always have only one header. Cells can often have larger parent headers spanning two columns, and subheads spanning one column each underneath that.

    View this link on the WC3 for an example. It’s a paragraph or two down the page.

    http://www.w3.org/TR/html401/struct/tables.html#adef-headers

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

I am a creative.

A List Apart founder and web design OG Zeldman ponders the moments of inspiration, the hours of plodding, and the ultimate mystery at the heart of a creative career.
Career