Tuesday, February 19, 2013

Responsive Tables

Responsive Tables
A few smart folks have already put together their thoughts on responsive tables and, while I think the proposed methods are pretty good, I think there might be room for improvement. As such, I've been tinkering for a while and came up with the following strategy when it comes to tables.

Step 1: Use data-* attributes to hold information about the column header(s) associated with the markup:

Name Email Dept, Title Phone

Smith,
Laura


Biology, Director
123-456-789

Johnson,
Ron


Purchasing, Director
123-456-7891

Step 2: When the screen is below a certain threshold, set the table elements to display: block (thereby linearizing the table), hide the thead where assistive tech won't see it and use generated content to expose the data-* attributes. Here's a snippet of SASS & Compass that does that:

// undo tables for small screens
// $break-4 is the px-width break at which you want to cut it off
@media (max-width: px-to-ems($break-4 - 1px)) {
  
  // make each table separate from other ones
  table {

    border: 0;
    @include trailing-border;
    padding-bottom: 0;
    display: block;
    width: 100%;
        
    // make sure captions are displayed
    caption {
      display: block;
    }
    
    /* 
     * wipe the thead from the face of the earth
     * modern screen readers will expose the 
     * generated content
     */
    thead {
      display: none;
      visibility: hidden;
    }
    
    /*
     * make everything display block so it 
     * aligns vertically
     */
    tbody, tr, th, td {
      border: 0;
      display: block;
      padding: 0;
      text-align: left;
      white-space: normal;
    }
    
    // give each row a little space
    tr {
      @include trailer;
    }
    
    /* Labeling
     * adding a data-title attribute to the cells
     * lets us add text before the content to provide
     * the missing context
     * 
     * Markup: 
     *   <td data-title="Column Header">Content Here</td>
     * 
     * Display:
     *   Column Header: Content Here
     */
    th[data-title]:before,
    td[data-title]:before {
      content: attr(data-title) ":\00A0";
      font-weight: bold;
    }
    th:not([data-title]) {
      font-weight: bold;
    }
    
    // hide empty cells
    td:empty {
      display: none;
    }
  }
}

We’ve been using this approach on a number of sites currently in development and it works really well. I put together a demo of this technique so you could play around with it yourself.

Notes:

  1. I chose to use a data-* attribute (data-title) instead of title as the title attribute could be read out by assistive technology and in the case of the thead being available as well (when not display: none), resulting in the information being read twice (which is not ideal). That's not a certainty however, so you could choose to go the title route if that's your preference. I prefer to avoid the potential issue.
  2. If you have multiple header rows over a cell (say a parent row and then a child row), I'd recommend making the data-title something like "Parent Header - Child Header".
  3. While you could use JavaScript to auto-generate the data-title attributes by referencing the column headers, I feel this is information that should exist even if JavaScript is not available. You may disagree.

View Demo