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 (37)
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.
Posted August 28, 2007 1:39 AM | #
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.
Posted August 28, 2007 2:27 AM | #
Wowwwwww man, it's beautiful tip!!!!
Posted August 28, 2007 7:28 AM | #
Your exampel links are all broken.
But nice technique.
Like it.
Posted August 28, 2007 8:04 AM | #
Example links now fixed. Thanks.
Posted August 28, 2007 8:44 AM | #
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!
Posted August 28, 2007 7:05 PM | #
It is not accessible for someone with JavaScript disabled. Nice work though with the basic functionality, but the usability should be tweaked.
Posted August 29, 2007 8:00 AM | #
@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.
Posted August 29, 2007 8:27 AM | #
Excellent article CSS Guy... Added site to my RSS
Posted August 29, 2007 2:55 PM | #
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
Posted August 29, 2007 2:58 PM | #
There's actually a better way through the use of <col class="highlighted_col"> tag for tables
Posted August 30, 2007 2:42 PM | #
"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.
Posted August 30, 2007 3:32 PM | #
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.
Posted August 31, 2007 7:22 AM | #
Pretty well explained article, but I myself would stick to tablelss.
Posted September 3, 2007 2:35 AM | #
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.
Posted September 4, 2007 9:04 AM | #
This was great. Thanks! You make it look so easy!
Posted September 5, 2007 3:24 PM | #
Great!
Would be even better if you handle CSS - on, JS - off situation.
Posted September 18, 2007 4:29 PM | #
Sorry I meant CSS - on, images - off situation...
Posted September 18, 2007 4:30 PM | #
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
Posted September 23, 2007 1:20 PM | #
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
Posted September 24, 2007 8:46 AM | #
Very nice article!
Could you make column highlited at the moment mouse hovers over it (ie. before clicking to chose).
Posted October 2, 2007 4:37 AM | #
I really like this! It's going to serve as inspiration when adding our new hosted pricing plan to our site.
Thanks for sharing.
Posted October 31, 2007 1:17 AM | #
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.
Posted December 11, 2007 3:18 PM | #
Thank you for sharing that... it is very nice!
Posted December 11, 2007 8:37 PM | #
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.
Posted February 22, 2008 12:53 AM | #
Thanks a lot. Your post help me for design my website.
Posted February 22, 2008 3:20 AM | #
Will use it on my site as well.
Posted February 23, 2008 4:35 AM | #
This is great trick.Thanks a lot.
Posted February 23, 2008 6:55 PM | #
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.
Posted February 25, 2008 7:55 AM | #
How do I pass the selected column value to the form? Great article by the way
Posted March 4, 2008 4:47 PM | #
@JD:
One way is to use hidden inputs.
Posted March 4, 2008 5:04 PM | #
I'm using classic ASP, so I'm not sure how the hidden inputs would be changed without a full page refresh. Thanks
Posted March 4, 2008 5:59 PM | #
I'm using classic ASP, so I'm not sure how the hidden inputs would be changed without a full page refresh. Thanks
Posted March 4, 2008 5:59 PM | #
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.
Posted March 4, 2008 7:02 PM | #
ok, muchas gracias exelente post!!
Posted March 7, 2008 11:55 AM | #
This is great trick. Thanks a lot.
Posted April 6, 2008 12:16 PM | #
It is very nice! thanks
Posted April 15, 2008 2:17 PM | #