Grow your CSS skills. Land your dream job.

Feature Table Design

Published by Chris Coyier

I ran into the feature table design from Shopify over on Pattern Tap and I was like DAMN SHOPIFY, that is one sexy table. I was inspired to try and replicate it. First in Photoshop, then in HTML/CSS. Recreating cool stuff you find on the web is definitely an excercise I recommend (a few days after, I read this - couldn't agree more). As these exercises typically do, it lead me down some interesting paths.

Here's my knockoff:

View Demo   Download Files

The Markup

Here is the abreiveated HTML:

<table id="feature-table">
  <colgroup class="basic"></colgroup>
  <colgroup class="plus"></colgroup>
  <colgroup class="premium" id="featured"></colgroup>
  <colgroup class="pro"></colgroup>
			<th id="header-basic"><span>$15 Basic</span> <a class="button" href="#">Sign Up</a></th>
			<th id="header-plus"><span>$35 Plus</span><a class="button" href="#">Sign Up</a></th>
			<th id="header-premium"><span>$99 Premium</span><a class="button" href="#">Sign Up</a></th>
			<th id="header-pro"><span>$150 Pro</span><a class="button" href="#">Sign Up</a></th>
			<td>50 pages</td>
			<td>75 pages</td>
    <!-- More rows here -->

Pretty clean. The only thing that isn't perfectly clean is the <span> in the header. There is all that fancy gradient fancy font stuff going on. Theoretically, it could be done with a boatload of CSS3, but you know, sometimes an image is just fine dang nabbit, especially when it's perfectly accessible CSS image replacement.

Of note is the <colgroup> element which I feel is underutilized. Colgroups allow you to target an entire column of table cells and apply styling, even though those table cells aren't actually descendants of the <colgroup>. Kind of a weird concept, but it works, and it's easier than applying a class name to every single table cell signifying what column it's in.

If you need some quick markup, I have some you can copy and paste on HTML-Ipsum.


Each header cell (<th>) has an ID. We'll set a static height on those and set background images for them. Vertically aligning to the bottom with a bit of bottom padding allows us to place the link button evenly. The span in those headers is kicked off the page via absolute positioning (i.e. accessible hiding).

#feature-table th { height: 120px; padding-bottom: 14px; vertical-align: bottom; }
#header-basic { background: url(../images/header-15.png) no-repeat; }
#header-plus { background: url(../images/header-35.png) no-repeat; }
#header-premium { background: url(../images/header-99.png) no-repeat; }
#header-pro { background: url(../images/header-150.png) no-repeat; }
#feature-table th span { position: absolute; top: -9999px; left: -9999px; }

Speaking of those buttons. I just used the CSS Button Maker to quickly design a button I thought looked nice and fit with the color scheme, and copy and pasted the CSS into this demo.

To color the cells, I set a fallback hex code color and then an HSLa color value. These class names are targeting the colgroup elements.

.basic   { background-color: #d5e4bc; background-color: hsla(85,  30%, 80%, 1); }
.plus    { background-color: #c1dcb7; background-color: hsla(110, 30%, 80%, 1); }
.premium { background-color: #bad6c8; background-color: hsla(150, 30%, 80%, 1); }
.pro     { background-color: #bbd3dc; background-color: hsla(190, 30%, 80%, 1); }

The final product is zebra striped and has that "featured column" thing going on. but we can approach that with JavaScript...

The JavaScript

jQuery, obviously. We can apply the "odd" class to odd table rows, as well as a "final-row" class with jQuery super easily:

$("tr:odd").addClass("odd");  // Zebra action
$("tr:last").addClass("final-row");  // For extra padding

The final row has extra padding

To handle the "featured" column, and keep things semantic, we just apply an ID to the colgroup:

<colgroup class="premium" id="featured"></colgroup>

Now the JavaScript needs to figure out which number column that is.

// Figure out which column # is featured.
var featuredCol;
$("colgroup").each(function(i) {
    if ( == "featured") featuredCol = i+1;

Now we're going to loop through all the table cells and figure out if the cell is in the column right before the featured column (if it is, apply a "leftOfFeatured" class) or in the column right after the featured column (if it is, apply a "rightOfFeatured" class).

While we are at it, we might as well apply class names to all table cells indicating their column. Colgroups were supposed to eliminate that need, but it turns out they have a fairly significant weakness. You can't do something like:

.basic .odd { 
      .basic is the colgroup, .odd is the row
      the row really isn't a descendant of the colgroup 
      in other words, this doen't work

This design calls for different color alterations depending on the column. So while we are running that loop, we'll just apply class names to the table cells and use those class names to do our bidding.

// Apply classes to each table cell indicating column
// Also applies classes if cell is right or left of featured column

var numCols = $("colgroup").length;

$("td, th").each(function(i) {
    $(this).addClass("table-col-" + ((i % numCols) + 1));
    if (((i%numCols)+1) == (featuredCol-1)) $(this).addClass("leftOfFeatured");
    if (((i%numCols)+1) == (featuredCol+1)) $(this).addClass("rightOfFeatured");

The variable i in this function is the index. It basically tells us what iteration of the loop we are on. So the 50th table cell it finds, the index will be 49 (index is zero-indexed). So if we take the index modulus the number of columns (determined by testing the length of the jQuery set) and add one, we'll have what number column the cell is in. Example: 4 columns, 10th cell found. 9 % 4 = 1, plus 1 is 2, so the 10th cell is in the 2nd column. And thus, that cell gets the class "table-col-2".

With the row .odd classes and the new table-col-x classes, we can now truly zebra stripe as the design demands:

.odd .table-col-1 { background-color: #edf3e2; background-color: hsla(85,  30%, 94%, 1); }
.odd .table-col-2 { background-color: #edf3e2; background-color: hsla(110, 30%, 94%, 1); }
.odd .table-col-3 { background-color: #edf3e2; background-color: hsla(150, 30%, 94%, 1); }
.odd .table-col-4 { background-color: #e2ecf0; background-color: hsla(190, 30%, 94%, 1); }

Zebra action complete.

Notice it's hex code fallbacks and HSLa again. The fun of using HSLa here was that the values are exactly the same except for the third value (the "lightness"). We increase that value 14% and that is the noticeable difference in tone.

The "leftOfFeatured" and "rightOfFeatured" classes apply a background image, just an alpha-transparent PNG shadow aligned and repeating to the left or right respectively.

.leftOfFeatured  { background-image: url(../images/shadow-left.png);  background-repeat: repeat-y; background-position: right center; }
.rightOfFeatured { background-image: url(../images/shadow-right.png); background-repeat: repeat-y; background-position: left  center; }


So that's it folks. It was fun for me to try and recreate something I thought was awesome, but just in a "this is how I would do it" kinda way rather than peaking at any of their code. I highly recommend this kind of exercise, if your schedule allows.

View Demo   Download Files


  1. Very nice knockoff if I do say so myself.

  2. Hey Chris,

    This is a really awesome article and I don’t think it just has to applies to HTML and CSS. This really inspires me to try out stuff in my latest post about graphic design inspiration on

    It would be cool to try to re-create beautiful designs to enhance my own sense of it.

    Cool article,


  3. Just what the doctor ordered THANKS in a million Chris YOU THE MAN !!!

  4. Nice article, there’s a typo though. excise should be exercise.

  5. That looks really good, and nicely put together. Useful stuff for a design-n00b like me :)

    On a very tangential note, I saw you had an ad for ‘HTML Rockstars’ on your demo page. I had a look at their site, and viewed source. Jesus, that’s some freaky looking markup.

    Every line is broken before an HTML attribute. It just looks wrong. It passes validation though so I guess that’s a good thing…

  6. Never thought we could use tables to do this.

    Perfect. This is so cool.

  7. wow, Thanks Chris for the kind words and not being so harsh on my mark up – it could definitely be improved :)

  8. Chris,

    Not sure how well this would work or not, but I bet you could also so some cool styling if you add a hover property to the colgroup. Would love to see an update with something like that.


  9. Hey Chris, Yet another incredible article with example. I use your examples a lot to learn from and they are so invaluable to me. When I get a chance I refer others to your website so they can learn from the master :) You da man Chris.

    Keep the good stuff coming and thanks for all you do buddy.

    Jim Zak

  10. Does the <colgroup> basically do the same thing or negate the need to have <th scope=”col”>? I don’t think I’ve ever used colgroup before.

    A summary attribute in your table would be a nice addition.

    • I’d admit I rarely if ever have used scope. I think the point of scope is more accessibility in declaring which set of cells this is a header for. For example, in this table the <th> elements are the header for a column of information, but a <th> could just as easily be used to head up a row of data. So you should scope it to declare which it is.

      <colgroup> on the other hand literally lets you apply styling to a column of cells despite the fact that the <colgroup> element doesn’t actually “wrap” any of those cells.

  11. This is great, i wouldn’t use jQuery to apply any magic here but i simply love your phylosophy on all of this Chris.

    Exploring the options, breaking it down, building it out and meeting the end goal in whatever means suits you at that given moment.

    100% of the time your articles technology wise are perfectly fine and suitable.

    Now if you had used Flash to achieve the above that would be funny. I dare you.

  12. Pretty impressive, Chris! I like what you said about recreating the things that inspire you – I definitely agree.

    As a side note, I was wondering why you used jQuery to add alternating styles instead of :nth-of-type? Seems like the perfect application for that selector :)

  13. Stan
    Permalink to comment#

    awesome!!! chris you have madskilz…

  14. Permalink to comment#

    Thanks so much. This article is so awesome :) One question, how can we convert PSD price table into HTML? It’s because there are many nice PSD price table online but I wonder how people can code them up in CSS?

  15. Permalink to comment#

    Excellent Chris. Very easy to follow and a great base for creating excellent table designs.

  16. sage
    Permalink to comment#

    I would have put the “Featured” column a bit wider.

    The shadow let thing that the column is on the foreground, thus should appear larger.

    I went to Shopify, and that’s what they did.

  17. Permalink to comment#

    Hey, thanks for sharing your ideas.

    Just a small hint: instead of looping through the elements we could use the “index()” method of jQuery, which is really handy i think.

    // Figure out which column # is featured.
    var featuredCol = (“colgroup”).index($(“#featured”));

  18. Great article as always.. ;)

  19. tom317
    Permalink to comment#

    first, quite boring stuff…
    second, and more important, why do you use javascript when it can all be done with css alone? isn’t this page called CSS-tricks? sure, tr:odd / tr:nth-child don’t work with every browser but it is css, so you should mention it at least!
    you could do the whole thing without hundreds of class attributes (and without js!), nth-child ftw!
    one more thing… why jQuery every time? did you ever try another js-framework (ok, jQuery is not a framework but a toolkit…)? obviously not… this site isn’t named “jQuery-Tricks”, is it?
    sorry, but i’m getting really annoyed seeing stupid jQuery instead of slick CSS here every time, again and again…

    • Johnny
      Permalink to comment#

      Quite agree – lets have a bit more CSS, a bit less image-replacement, and a lot less jQuery – if you really must do something with Javascript, then at least do it in pure Javascript so that anyone can make use of it.

    • I know, really, this blog is really going to shit. The author always posts stuff he finds personally interesting with technologies relevant to what he knows and relates to his working life. He should just shut up and start doing roundups like any decent web design blog. We should all unsubscribe and start trolling Digg again.

    • MoonChild
      Permalink to comment#

      Hahaha, Nicely done Chris, nicely don

    • I think tom317 has a point. Nonetheless, this post does have the ‘CSS tricks’ and then some.

      Yes, it has more than a few ‘CSS tricks’ which makes it look like an off-topic for this site, but if you start with the CSS tricks it’s interesting to see what other things are co-related to those tricks: the scripting and the teaching of trying to mimic something we see on the web.

      I think what’s killing Chris’ site is the name: is not about CSS tricks only or anymore, is about CSS tricks, scripting, web design advice in general (and in detail some times).

      In other words, Chris creates Web Design content but frames it in the term “CSS Tricks”, and that’s where I think tom317 has a point.

    • Grr, blog names! Smashing Magazine should definitely be writing articles about smashing things. Noupe should focus more on “noupe”-ing things.

      It’s a name folks. Build that bridge and get over it.

      Chris, don’t stop what you’re doing man, I love your jQuery tuts, tips, and TRICKS.

  20. Webling
    Permalink to comment#

    thanks for sharing. though i would place this kind of data in an definition list.

    @tom317: i´m sure you´ll find enough pure-css-articles nth-child/odd releated on css-tricks :-D

  21. Very nice but I think I’m more impressed by your demo page, lol.

  22. Chris,
    I love your philosophy on learning from others. As a musician trying to learn my way in this web programming world, I loved the analogy on the 37 signals link about learning parts to tunes when you start.

    I feel this web stuff is so similar to music in that way. The difference is there is so much crap on the web. Thanks for giving so much back to the community you learned from and providing us web hacks with your “web tablature.”

  23. i like seeing well done tables. clean and crisp.

  24. Mimo
    Permalink to comment#

    HI Chris,
    I am a long time follower, first time commenter.

    It looks different in IE compared to firefox. Also if you wanted the shadows on the last row, how do i go about doing this.


    • Mimo
      Permalink to comment#

      It’s okay, I got it.

      Messed about with the javascript values.

      great post

  25. Daniel Mooney
    Permalink to comment#

    Hey there,

    Great little article. I found you by doing a little research into the jQuery accordion, and found your little snippet of the grid accordion. Decided to browse the site further, and here I am…

    A question, though: your solution makes use of a TH w/ class name to define background image and a span that places the text (for screen readers) off the page. Why use a class and span when simply using an IMG tag w/ title (i.e. “title=’$15 Basic'”) would scratch the screen reader’s itch?

    Am I missing something?

  26. Colgroups seem like they’re great for setting backgrounds and accessing via javascript, but I was surprised to find their limitations when styling using font-weight, color, etc.

    Are we just stuck adding classes to a specific column we’re looking to style that column’s text?

  27. This is seriously great advice, thanks a lot.

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".