Creating a table with dynamically highlighted columns like Crazy Egg's pricing table
Update Aug 29, 2007: Examples 9 and 10, and the downloadable zip now accommodate for JavaScript-disabled browsers.
Update Aug 28, 2007: links to examples fixed. Again.
I like Crazy Egg's pricing table on their Pricing & Signup page. When you click on "Sign Up" for an option, that plan's column highlights, the other plans vanish, and a signup form takes their place. There is a number of impressive things happening within this small area. I wanted to try and recreate the behavior step by step, and share the power of combining CSS, JavaScript, and images in clever ways.
If you just clicked over to look at Crazy Egg's site, then clicked back, consider visiting there again when you're finished reading this tutorial. If you're as impressed with the way their site is put together as I am, you, or someone you know/work with, could probably benefit from Crazy Egg's amazing click tracking visualization tools. Their prices sure make it easy to get started, so give it a consideration while you're giving them traffic.

For those who like to skip ahead: view the final example or download a zip with all the html, CSS, JavaScript, and supporting images used in this article.
Getting Started
First I need a table, and I'll model my example off of Crazy Egg. I'll use a left column for the descriptions, a top row for headings, and a matrix of features in the body. We're going to make use of thead, tfoot, and tbody.
<table>
<thead>
<tr>
<th>'s go here
<tr>
</thead>
<tfoot>
<tr>
<td>'s go here
</tr>
</tfoot>
<tbody>
<tr>
<td>'s go here
</tr>
</tbody>
</table>
View example 1, the bare table.
Next Step: Add class
By default, all the table cells look alike. To establish that cells in each column are related, I need to add classes to all the <td> and <th> tags.
<table>
<thead>
<tr>
<th class="desc">
<th class="choiceA">
<th class="choiceB">
...
<tr>
</thead>
<tfoot>
<tr>
<td class="desc">
<td class="choiceA">
<td class="choiceB">
</tr>
</tfoot>
<tbody>
<tr>
<td class="desc">
<td class="choiceA">
<td class="choiceB">
</tr>
</tbody>
</table>
View example 2, adding class, which also shows the checkbox added. Here's a screenshot.

Next Step: Establish what a selected state looks like
We'd like to indicate a selected column by adding class="on" to the appropriate cells. Since all cells already have a class name assigned, the on class name will added to the class attribute value with a space.
<table>
...
<tbody>
...
<tr>
<td class="desc">
<td class="choiceA on">
<td class="choiceB">
</tr>
...
</tbody>
</table>
View example 3, the selected state.

Next Step: Switching selected columns with JavaScript
When someone hits the "Choose" button in a particular column, we want that entire column to take on the selected state. We need a JavaScript for that.
In English, here's what we want to happen:
- When someone clicks "Choose" for choice A
- remove the class name of "on" from all other cells
- then, take every cell with the class for choice A
- and add " on" to it.
Here are the tools needed to do that:
- A javascript function called getElementsByClassName, which allows us to target elements based on their class value. (I'm using one written by Robert Nyman)
- A javascript function called addClassName (also via Robert Nyman, although the one I'm using he hasn't placed on his web site yet. This function will be used to add "on" as a class name to the selected columns.
- A javascript function called removeClassName, which is used to unselect cells by removing the "on" value from their class attributes (via Robert Nyman)
- A custom javascript function to tie the behavior together.
Attach the custom function to the "Choose" link's onclick event:
...
<tfoot>
<tr>
...
<td class="choiceA">
<a href="#" onclick="activateThisColumn('choiceA');return false;">
Choose
</a>
</td>
<td class="choiceB">
<a href="#" onclick="activateThisColumn('choiceB');return false;">
Choose
</a>
</td>
...
</tr>
</tfoot>
...
View example 4, selectable columns. Try it - it works!
Next Step: Introducing Images
If we were just changing background colors, it wouldn't have nearly the same coolness as the Crazy Egg table, whose selected column seems to pop out, or extend beyond the boundaries of the table.

It's obvious that a background image is used with the selected table header cell. What isn't obvious is that the neighboring table header cells are also using background images, which have a solid color on the bottom, and white along the top. This is evident when turning on cell outlines using Firefox's Web Developer toolbar:

I'm going to make some images that accomplish the same thing. Here are the images I'll be using:
| regular <th> | ![]() |
| selected <th> | ![]() |
| left column <th> | ![]() |
| regular <td> | ![]() |
| selected <td> | ![]() |
| left column <td> | ![]() |
| regular <td> in tfoot | ![]() |
| selected <td> in tfoot | ![]() |
| left side <td> in tfoot | ![]() |
| choose button | ![]() |
View example 5, images and cell dimensions in place. Looking good - we now have a decent-looking table with some dynamic behavior.
But we can't stop. Let's introduce even more dynamic behavior.
Next Step: Showing a form, hiding some table columns.
On Crazy Egg, when a column is selected, all the other columns disappear, the selected column moves to the left, and a form appears in the space to the right. The cancel button of the form then reshows all the other previously hidden columns.
First, I'll draw out the form that will exist outside the table.
View example 6, a form is born.
Then we must style the form to take up the exact dimensions of the four unselected columns.
View example 7, a form is styled and dimensionalized.
The form will have to be absolutely positioned over table. So we'll introduce a container element to both the form and the table to be our relative positioned parent.
<div id="prices">
<div id="formcontainer">
...
</div>
<table id="pricetable">
...
</table>
</div>
View example 8, a form is positioned.
Ooops! The form shouldn't show by default, and we need to have all other cells except the cells in the selected column and cells in the far left column. We'll make use of a new function, hasclass, that checks for class="side". We'll modify our JavaScript function "activateThisColumn" to only hide and show the appropriate cells.
function activateThisColumn(column) {
var table = document.getElementById('pricetable');
var form = document.getElementById('formcontainer');
// first, remove the 'on' class from all other th's
var ths = table.getElementsByTagName('th');
for (var g=0; g<ths.length; g++) {
removeClassName(ths[g], 'on');
if (!hasclass(ths[g],'side')) {
ths[g].style.display = 'none';
}
}
// then, remove the 'on' class from all other td's
var tds = table.getElementsByTagName('td');
for (var m=0; m<tds.length; m++) {
removeClassName(tds[m], 'on');
if (!hasclass(tds[m],'side')) {
tds[m].style.display = 'none';
}
}
// now, add the class 'on' to the selected th
var newths = getElementsByClassName(column, 'th', table);
for (var h=0; h<newths.length; h++) {
addClassName(newths[h], 'on');
newths[h].style.display = '';
// not all browsers like display = 'block' for cells
}
// and finally, add the class 'on' to the selected td
var newtds = getElementsByClassName(column, 'td', table);
for (var i=0; i<newtds.length; i++) {
addClassName(newtds[i], 'on');
newtds[i].style.display = '';
// not all browsers like display = 'block' for cells
}
// show the form!
form.style.display = 'block';
}
View example 9, a form is revealed when requested.
But we still need to hide the form and keep our selected columns when the form is cancelled. We'll add a function to the onclick event of the cancel button.
<input type="image" alt="Cancel" src="i/button_cancel.gif" onclick="hideTheForm();return false;" />
And the function says:
function hideTheForm() {
// get the form
var form = document.getElementById('formcontainer');
// hide the form
form.style.display = 'none';
// now get the hidden table cells and show them again
var table = document.getElementById('pricetable');
var tds = table.getElementsByTagName('td');
for (var i=0; i<tds.length; i++) {
tds[i].style.display = '';
}
var ths = table.getElementsByTagName('th');
for (var k=0; k<ths.length; k++) {
ths[k].style.display = '';
}
}
View example 10, a form is revealed when requested, and dismissed from view when cancelled.

Download a zip with all the html, CSS, JavaScript, and supporting images.
There's more going on with Crazy Egg's form than what I've mentioned in this article. Mainly, Crazy Egg invokes a JavaScript library to animate the form's appearance rather than switching from 'none' to 'block' and back again so bluntly. That extra step is beyond the scope of this article, but that kind of detail and work put into the form gives a clear indication that the folks at Crazy Egg really know what they are doing. (Also, IE has some display issues for my form, but those could easily be overcome with conditional comments.)
I hope you enjoy the post, and see what some scripting packaged with images and CSS can accomplish in a small amount of space.











Comments (52)
sudopeople said:
Nice! I really like this technique, I can see it being applied for tons of other things.
I think I would put a "Cancel" or "Change" button where the "Choose" button is when it is selected. My first thought was to click there again to go back to all of them. I also think that graying out the button, like the original table on Crazy Egg, is superfluous. There is a perfectly good button there ready to use and it's rendered useless by way of extra programming.
Good article.
# August 28, 2007 1:39 AM
Jermayn Parker said:
Good to have it broken down in simple and easy to follow terms..
btw the links to the breakdown of the steps are not working for me.
# August 28, 2007 2:27 AM
Pedro Rogério said:
Wowwwwww man, it's beautiful tip!!!!
# August 28, 2007 7:28 AM
John said:
Your exampel links are all broken.
But nice technique.
Like it.
# August 28, 2007 8:04 AM
CSS Guy said:
Example links now fixed. Thanks.
# August 28, 2007 8:44 AM
Thomas Aylott said:
Thanks for the excellent article!
I'm really psyched that people really appreciate all the work I put into this implementation. And the excellent design done by http://ApplesToOranges.com/
This was really really tricky to get working properly in all browsers. Basically I had to resort to using browser specific CSS tweaks to make it all line up properly. I usually try to avoid that.
I generally recommend avoiding tables at all costs. Mostly for semantic reasons, but also because of the horrible cross-browser issues it creates. Semantically this is the right thing to use for this data though.
If you think this stuff is neat though, you'll really have to check out how I got the reports working. Especially check out the paging stuff inside the mored open overlay panels. ;)
Thanks!
# August 28, 2007 7:05 PM
matt said:
It is not accessible for someone with JavaScript disabled. Nice work though with the basic functionality, but the usability should be tweaked.
# August 29, 2007 8:00 AM
CSS Guy said:
@Thomas:
I assume from your comments that you're the one that put this together for Crazy Egg. Nice work, and I'm glad you appreciate the article.
@Matt:
Good point. Crazy Egg overcomes this by making the "Choose" links have real destinations - a page devoted to sign-up. The form on the pricing page stays hidden. Examples 9, 10, and the downloadable zip now accommodate for JavaScript-disabled browsers.
# August 29, 2007 8:27 AM
Keith Donegan said:
Excellent article CSS Guy... Added site to my RSS
# August 29, 2007 2:55 PM
Jesper Rønn-Jensen said:
No use of <col> tag or other things to enhance accessibility as well for the html/table layout.
I bet you have some interesting thoughts about that.
Also, are there any cross-browser issues for using that particular HTML over -- for instance -- the col tag or other things mentioned in Verlee's "A CSS styled table version 2
# August 29, 2007 2:58 PM
!nso said:
There's actually a better way through the use of <col class="highlighted_col"> tag for tables
# August 30, 2007 2:42 PM
Christian said:
"I generally recommend avoiding tables at all costs."
I hope you do use them not for layout but when presenting tabular data, as in the examples shown above. Tables really do have a legitimate use. Many people don't understand that.
# August 30, 2007 3:32 PM
Murtaza said:
Your solution gives an ActiveX warning IE 7 while the one on Crazyegg does not. I am not a programmer, don't mind if the point is stupid.
# August 31, 2007 7:22 AM
Marko Mihelcic said:
Pretty well explained article, but I myself would stick to tablelss.
# September 3, 2007 2:35 AM
Montoya said:
Don't know if this has been mentioned already, but a good option would be to dynamically add the classes to the TD's with Javascript rather than doing it in the markup, in cases where it isn't easy to automate the process on the server-side. I don't have a code example to post but it wouldn't be hard.
# September 4, 2007 9:04 AM
Misty said:
This was great. Thanks! You make it look so easy!
# September 5, 2007 3:24 PM
FataL said:
Great!
Would be even better if you handle CSS - on, JS - off situation.
# September 18, 2007 4:29 PM
FataL said:
Sorry I meant CSS - on, images - off situation...
# September 18, 2007 4:30 PM
Hector said:
Great article. It was well explain, and I did not have any trouble following. I was looking at the javascript code borrow from Robert Nyman; and I think that the function activeThisColumn could be replace with the code below so that it not uses copywrite code .
function activateThisColumn(column){
var table = document.getElementById('pricetable');
var form = document.getElementById('formcontainer');
var cellheader = table.getElementsByTagName('th');
for(var j=0; j {
//remove 'on' from classes that have it
cellheader[j].className = cellheader[j].className.replace('on',"").replace(/^\s+|\s+$/g,"");
if (!hasClass(cellheader[j],'side')) {
cellheader[j].style.display = 'none';
}
}
var cellsbody = table.getElementsByTagName('td');
for(var i = 0; i {
//remove 'on' from classes that have it
cellsbody[i].className = cellsbody[i].className.replace('on', "").replace(/^\s+|\s+$/g, "");
if (!hasClass(cellsbody[i],'side')) {
cellsbody[i].style.display = 'none';
}
}
for(var j=0; j { //add 'on' on the selected column
if(cellheader[j].className == column)
{
cellheader[j].className = column + ' ' + 'on';
cellheader[j].style.display = '';
}
}
var cellsbody = table.getElementsByTagName('td');
for(var i = 0; i {
if(cellsbody[i].className == column)
{//add on the cell click
cellsbody[i].className = column + ' ' + 'on';
cellsbody[i].style.display = '';
}
}
form.style.display = 'block';
}
It is just a suggestion; it is not to offend Robert for his great work; or the CSS Guy.
Keep up the great work
# September 23, 2007 1:20 PM
polarizer said:
Nice article that makes it easy to do such excellent work on own pages by breaking it down into traceable steps.
@Hector: Thx for providing alternative code to circumvent certain issues.
polarizers 2 cents
# September 24, 2007 8:46 AM
freediver said:
Very nice article!
Could you make column highlited at the moment mouse hovers over it (ie. before clicking to chose).
# October 2, 2007 4:37 AM
erssa said:
I really like this! It's going to serve as inspiration when adding our new hosted pricing plan to our site.
Thanks for sharing.
# October 31, 2007 1:17 AM
rpgfan3233 said:
I think you could have made use of the colgroup and col elements of tables in this case. That way, you wouldn't have needed to type "class=..." so much. It seems redundant to me, and colgroup/col seem to help make it less so. Just a quick bit of info.
# December 11, 2007 3:18 PM
Tony Landis said:
Thank you for sharing that... it is very nice!
# December 11, 2007 8:37 PM
Luke said:
While this technique does work, looping through a live DOM structure and changing classes on each cell is horribly inefficient, and in this case unnecessary.
I think a better approach would be to have the respective "choose" links set the className on the table element to the class of the intended active column. No need to test for the presence of another class, just set tbl.className = theNewActiveColumnClass. Then in the css, define
.choiceA td.choiceA,
.choiceB td.choiceB,
.choiceC td.choiceC,
.choiceD td.choiceD {
/* all your active cell styles */
}
And so for the th elements as well.
One DOM change means one reflow, and there's no longer a need for any class inspection or modification methods.
# February 22, 2008 12:53 AM
pagines web said:
Thanks a lot. Your post help me for design my website.
# February 22, 2008 3:20 AM
David Jacques-Louis said:
Will use it on my site as well.
# February 23, 2008 4:35 AM
Jarek said:
This is great trick.Thanks a lot.
# February 23, 2008 6:55 PM
Rob said:
Excellent article, thanks.
I did notice a problem with the crazy egg table not rendering correctly with JavaScript off though. Part of the table gets hidden by a grey rectangle making it inaccessible.
# February 25, 2008 7:55 AM
JD said:
How do I pass the selected column value to the form? Great article by the way
# March 4, 2008 4:47 PM
CSS Guy said:
@JD:
One way is to use hidden inputs.
# March 4, 2008 5:04 PM
JD said:
I'm using classic ASP, so I'm not sure how the hidden inputs would be changed without a full page refresh. Thanks
# March 4, 2008 5:59 PM
JD said:
I'm using classic ASP, so I'm not sure how the hidden inputs would be changed without a full page refresh. Thanks
# March 4, 2008 5:59 PM
JD said:
ok, sorry for the post-fest but I figured it out. I added a function to activateThisColumn that checks to see what column was submitted and writes a hidden input field to the DOM containing the selected column name. Works great - thanks again.
# March 4, 2008 7:02 PM
ctraos said:
ok, muchas gracias exelente post!!
# March 7, 2008 11:55 AM
duplicate images said:
This is great trick. Thanks a lot.
# April 6, 2008 12:16 PM
pàgines web said:
It is very nice! thanks
# April 15, 2008 2:17 PM
Patryk Swieczkowski said:
Interesting effect could be done when change
onclick="activateThisColumn
to
onmouseover="activateThisColumn
in example05.html
# May 30, 2008 3:25 AM
PraP said:
This is really interesting and dynamic way to play with table.
# June 25, 2008 1:13 AM
Cole said:
I love the script I just don't know how to get the submit button to actually work?
# June 30, 2008 3:38 PM
CT said:
Exelente, grandiosa web, muchas gracias !! saludos desde chile
# July 6, 2008 12:58 PM
Maqsood said:
Its really a great css for table.
# July 11, 2008 11:49 AM
Johann said:
How would you implement the column select without the choose button.
I would like to click anywhere in the column to select that column.
Thanks
# September 30, 2008 9:10 AM
CSS Guy said:
@Johann:
With the existing code, take the onclick attributes that are on each 'choose' button, and attach them to all the <td>'s instead.
# December 18, 2008 5:44 PM
matematik said:
That's wonderfull.And very logically
# January 19, 2009 3:04 PM
Firma online said:
Great tutorial, thx !
# January 25, 2009 6:14 AM
Gregory said:
Hi,
I really enjoy this example and have been trying to modify it to highlight rows instead of columns... is there a simple way to do it? I've been trying to modify the code for days and im getting nowhere.
# February 8, 2009 10:23 PM
CSS Guy said:
@Gregory:
Doing rows instead of columns isn't so straightforward.
If you think you can do some backward engineering and filling in of blanks, you could check out this post, and try to work it from the final example. It's certainly not the same thing, but it's the closest I have for now.
# February 9, 2009 9:00 AM
Allan Cass said:
Very nice article. 10x.
# March 19, 2009 5:13 PM
saurabh shah said:
This is really awesome man ! keep rocking ...
# May 14, 2009 1:41 PM
Robin said:
Great! Thanks :D
One question:
I want an extra button (: info) beneath the choose button.
When clicked the same function should be activated but only loaded with an other div.
Dont know how :(
can anyone help me?
# June 19, 2009 8:07 AM
Allan Cass said:
Great article - a lot of details and code! Thank you!
# June 23, 2009 4:44 PM