Responsive Tables With CSS
02.02.2018
HTML Tables - The Bane of Responsive Design
HTML5 and CSS3 have tools to handle almost all content gracefully on any screen size. Almost any design can be achieved with smooth reflow for every device. The largest exception (and biggest headache for web developers) is the venerable HTML table! Tables simply do not resize nicely and, based on their content, they eventually will not get any smaller.
Traditionally, two approaches have been taken: either change the font size or add a horzontal scrollbar to the element containing the table. Both are terrrible solutions. Users are not comfortable or happy scrolling horizontally to view their data, and if the font size is small enough to fit the entire table on a small screen, users will simply zoom until they can read the font and then have to scroll horizontally anyway!
Lately, many developers have taken to avoiding tables. Instead, the data that would have been rendered inside of tables would be output as nested unordered lists or divs and styled to layout as tables on a large screen. From an accessibility standpoint this is not acceptable, and from a philosophical standpoint it is just wrong. If the content is tabular data it really OUGHT to be marked up as a table.
CSS To The Rescue
Thankfully, there is a fully-responsive and purely CSS solution. So, for a first step, go ahead and render tabular data as it ought to be - within HTML table tags. The solution is also simpler if you make proper use of <thead> and <tbody> elements.
<table>
<thead>
<tr><th>Size</th><th>Color</th><th>Price</th></tr>
</thead>
<tbody>
<tr><td>Small</td><td>Red</td><td>4.95</td></tr>
<tr><td>Medium</td><td>Blue</td><td>5.75</td></tr>
<tr><td>Large</td><td>Green</td><td>6.80</td></tr>
</tbody>
</table>
Next, CSS should be created to display the table with table cells stacked upon each other for small screens, rather than side-by-side as they are naturally displayed. This is one of the few times where the CSS is actually MORE complicated if you are designing your site mobile-first (which we all should be doing by now!)
Mobile-First CSS is USUALLY Simpler
Most HTML elements used for layout of a site are block elements, so they display stacked on top of each other by nature, which is just what we want on a small screen. CSS rules must be applied to get them to layout side-by-side for a larger screen, so layering those styles on with a media query leads to rather simple and straightforward stylesheets.
HTML tables are just the opposite. They natively display the way we wish them to on a large screen, and changes must be applied for smaller screens. Which would suggest that a desktop-first approach would lead to simpler CSS, and it would in this case. However, it is still not a recommended approach. This means that we will apply stylings to tables for small screens and then override those styles with media queries, changing the elements back to displaying the way they would have were no stylings applied whatsoever.
table {
border-collapse: collapse; }
thead {
display: none; }
tr {
display: block; padding: 0.5em;
border: 1px solid #BBB; }
td {
display: block; }
tbody tr:nth-child(even) {
background-color: #E0E0E0; }
tbody td:not(:last-child) {
border-bottom: 1px solid #DDD; }
@media screen and (min-width: 60em) {
thead {
display: table-header-group; }
thead tr {
background-color: #AAA; }
thead th:not(:last-child) {
border-right: 1px solid #999; }
tr {
display: table-row; padding: 0; }
td {
display: table-cell; }
tbody td:not(:last-child) {
border-bottom: none;
border-right: 1px solid #DDD; }
}
This works nicely, but on small screen sizes there are no labels displaying for the data. This is where the magic comes in! CSS :before selectors target the inside of an element, immediately before its existing content. They can be used to inject content into the document from within the stylesheet. Normally I would strongly oppose this on philosophical grounds because content is the purview of HTML, not CSS. However, when the PURPOSE of the content in question is styling the approach is absolutely acceptable. The really valuable piece of the solution is the CSS attr() function, which provides the capability to read the value of an attribute on an HTML element and use it for the content property. (In fact, the lastest CSS specification provides for the attribute's value to be used for any CSS property, but browser support is almost non-existent, yet - thankfully, the content property is just what is needed here.)
The desired labels just need to be supplied as an attribute value on each table cell where the CSS can pick them up and inject into each cell. The cells are then formatted with a large padding value to leave room for the label, and the label content is positioned within that margin.
<tr>
<td data-label="Size">Small</td>
<td data-label="Color">Red</td>
<td data-label="Price">4.95</td>
</tr>
td {
display: block; padding-left: 35%; }
td:before {
content: attr(data-label);
display: inline-block;
width: 33%; margin-left: -33%; }
@media screen and (min-width: 60em) {
td {
padding-left: 0; }
td:before {
display: none; }
}
You can view a working JSFiddle of the entire solution here. May all your code be groovy!