Feature Table Design

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>
	<thead>
		<tr>
			<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>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>50 pages</td>
			<td>75 pages</td>
			<td>Unlimited</td>
			<td>Unlimited</td>
		</tr>
    <!-- More rows here -->
	</tbody>
</table>

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.

The CSS

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 (this.id == "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; }

Conclusion

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