Saturday, January 8, 2011

Styling Tables with CSS

Learn how to use CSS to transform dull HTML tables into beautiful works of art. This article shows how to customize the borders, spacing, padding, background and colours of tables and table cells, as well as how to produce alternate-coloured table rows and create hover effects.

In this tutorial, we'll take a plain old HTML table:

... and transform it into something beautiful using nothing but CSS:

Techniques we'll cover include:

  • Adjusting the spacing, padding and margin of table cells
  • Styling the borders of the table and the cells within the table
  • Changing text and background colours, and using background images
  • Creating alternately-coloured table rows to aid legibility
  • Adding a mouse hover effect to table rows
  • Styling the table caption

The basic HTML table

Here's the markup for our basic, unstyled table. It contains:

  • A summary attribute, used to give a detailed description of the table's structure for non-visual browsers, such as screen readers.
  • A caption element. This gives a short description of the table's contents for non-visual browsers. It's also usually displayed by regular browsers, either above or below the table.
  • A header row, providing labels for Product, 1st Quarter, 2nd Quarter, and so on. We use a th (table header cell) element for each label to indicate that this is header information, rather than actual data. We also use the scope="col" attributes to indicate that these header cells refer to the columns of data below them.
  • Four data rows, containing the sales data for each widget. Each row starts off with another header cell (th), containing the widget name. This time, we use scope="row" to indicate that the header cell refers to the data in the same row. After the header cell, four data cells (td) contain the actual sales data.

<table summary="This table shows the sales figures for the four products in the Widget product line in 2007. The figures are broken down by quarter.">
<caption>Widget sales figures, 2007</caption>
<tr>
<th scope="col">Product</th>
<th scope="col">1st quarter</th>
<th scope="col">2nd quarter</th>
<th scope="col">3rd quarter</th>
<th scope="col">4th quarter</th>
</tr>
<tr>
<th scope="row">SupaWidget</th>
<td>$9,940</td>
<td>$10,100</td>
<td>$9,490</td>
<td>$11,730</td>
</tr>
<tr>
<th scope="row">WonderWidget</th>
<td>$19,310</td>
<td>$21,140</td>
<td>$20,560</td>
<td>$22,590</td>
</tr>
<tr>
<th scope="row">MegaWidget</th>
<td>$25,110</td>
<td>$26,260</td>
<td>$25,210</td>
<td>$28,370</td>
</tr>
<tr>
<th scope="row">HyperWidget</th>
<td>$27,650</td>
<td>$24,550</td>
<td>$30,040</td>
<td>$31,980</td>
</tr>
</table>



Giving the table a class



In order to style our table, we first give it a class, like this:





<table class="pretty-table" summary="This table shows the sales figures for the four products in the Widget product line in 2007. The figures are broken down by quarter.">



This allows us to identify any tables with the pretty-table class in our CSS, so that we can apply our styling. To do this, we use the .pretty-table selector in the CSS.



Controlling cell spacing and padding



In HTML 4 and XHTML 1.0, you can control the spacing between each table cell using the cellspacing attribute, and the padding between each table cell and the content inside it using the cellpadding attribute. However, you can also control these two attributes within CSS:





  • To control cell spacing, use border-spacing. For example: table { border-spacing: 5px; }.




  • To control cell padding, use padding on individual table cells. For example: td { padding: 5px; }.




Unfortunately, IE6 doesn't recognize border-spacing. However, all is not lost. We want our styled table to have no gaps between table cells, so we can instead use border-collapse: collapse;, which IE6 does understand.



So here's our CSS to control spacing and padding. We want no gaps between table cells, and we want a padding of 0.5 ems around the content within each table cell:





.pretty-table { border-collapse: collapse; }
.pretty-table th, .pretty-table td { padding: 0.5em; }



Styling table borders



By default, most modern browsers display tables with no borders around the table cells. Using CSS, you can fine-tune the appearance of all borders within a table, including:





  • The border around individual table cells




  • The border around table rows




  • The border around the whole table




Borders around the table and table cells


Let's start by placing a 1-pixel dark grey border around the whole table, and a 1-pixel lighter grey dotted border around each table cell:





.pretty-table { border: 1px solid #333; }
.pretty-table th, .pretty-table td { border: 1px dotted #666; }



Two thick borders to separate headers from data


We also want to place a 2-pixel dark grey border to the right of the left hand column, and another below the top row, to separate the header cells from the data cells.



First of all, let's place the border below and to the right of the top-left cell ("Product"). To select this cell in CSS, we can use an attribute selector, as follows:





.pretty-table th[scope=col]
{
border-bottom: 2px solid #333;
border-right: 2px solid #333;
}



The [scope=col] attribute selector allows us to select just those table header cells that have the attribute scope="col", i.e. our top table row.



However, we only want that right-hand thick border on the "Product" cell, not the other four cells in the row. So we need to select those other four cells, and reset their right-hand borders to the standard dotted border. To select the cells, we can use an adjacent sibling selector, as follows:





.pretty-table th+th[scope=col] { border-right: 1px dotted #666; }



Adjacent sibling selectors allow you to select an element based on the element immediately before it. So in the above code, we're selecting only those th elements that are preceded by another th element - in other words, all header cells except the first one ("Product"). We then reset those selected cells back to the standard dotted border.



Finally, we'll add the same 2-pixel border to the right hand edge of the left-hand column by selecting all those header cells with the scope="row" attribute:





.pretty-table th[scope=row] { border-right: 2px solid #333; }



Setting table and cell colours and backgrounds



Next, let's take a look at controlling the background and foreground colours in our table, as well as setting background images.



As with borders, CSS gives you control over the colour and background of:





  • individual table cells




  • table rows




  • the whole table.




The base text colour


We'll start by setting the basic text colour of our cells. In our example, we'll use a dark maroon colour:





.pretty-table th, .pretty-table td { color: #632a39; }



Colouring the header cells


Now we'd like to use different background and foreground colours for our header cells, as follows:





  • The "Product" cell has black text (#000) and a mid blue background (#8fadcc).




  • The remaining header cells along the top row have white text (#fff) and a dark blue background (#7d98b3).




  • The header cells down the left hand side (apart from "Product") have a light blue background (#b8cfe5). We'll leave the text colour at the default dark maroon.




To set colours for these different types of cells, we can use attribute and adjacent sibling selectors again, exactly as we did when setting cell borders above:





/* Set colours for all header cells in the top row */
.pretty-table th[scope=col] { color: #000; background-color: #8fadcc; }

/* Set colours for all header cells in the top row except "Product" */
.pretty-table th+th[scope=col] { color: #fff; background-color: #7d98b3; }

/* Set the background colour for all header cells in the left column */
.pretty-table th[scope=row] { background-color: #b8cfe5; }



Note how the second selector overrides the first, as it is more specific (th+th versus th). This ensures that only the "Product" cell is left with the black text and #8fadcc background. (For more on this topic, see Calculating a selector's specificity in the CSS 2.1 specification.)



Table background image



Adding a gradient to the table background


As well as changing text and background colours of table elements, you can also give those elements background images. Let's use this to add a gradient background to the whole table.



To do this, first create a suitable background image. You can see the one we've used over on the right. It's the right height for the table, and 16 pixels wide.



We use this image as the table background as follows:





.pretty-table
{
background: #bcd0e4 url("widget-table-bg.jpg") top left repeat-x;
}



This sets our background image on the table, positions it at the top left corner of the table, and makes it repeat horizontally. We've also told the browser to use the background colour #bcd0e4 for any areas of the table not covered by the image. This means that the table retains its blue background if it grows to be longer than the height of the image.



Read more on controlling backgrounds with CSS.



Creating table rows with alternate colour



A nice trick to aid readability of a table is to colour alternate table rows in a different colour. To do this, we first need to add a class to those alternate rows in the table's markup:





<tr>
<th scope="row">SupaWidget</th>
...
</tr>
<tr class="alt">
<th scope="row">WonderWidget</th>
...
</tr>
<tr>
<th scope="row">MegaWidget</th>
...
</tr>
<tr class="alt">
<th scope="row">HyperWidget</th>
...
</tr>



Now we can select those rows in CSS, and apply a different colour to the text in the cells within those rows — say, a dark blue:





.pretty-table tr.alt th, .pretty-table tr.alt td { color: #2a4763; }



Adding a hover effect to table rows



We can further improve readability of our table by making a table row change colour when the mouse hovers over it. We'll use white text and a maroon background as the hover colours, making sure to only add the effect to those rows that contain data (i.e. not the top row):





.pretty-table tr:hover th[scope=row], .pretty-table tr:hover td
{
background-color: #632a2a;
color: #fff;
}



Changing the look of the table caption



By default, the table caption tends to appear centred above the table, though this is very much browser-dependent. We can use the CSS caption-side property to specify a top or bottom caption, and also apply other standard CSS properties such as text-align and padding to achieve a nice-looking caption:





.pretty-table caption
{
caption-side: bottom;
font-size: 0.9em;
font-style: italic;
text-align: right;
padding: 0.5em 0;
}



The final markup and CSS



Our table now looks much prettier! Here's the final code. We've made a few small additional changes, such as setting a nice font, specifying font sizes, and making the top header row uppercase, to produce a great finish.



The table markup:





<table class="pretty-table" summary="This table shows the sales figures for the four products in the Widget product line in 2007. The figures are broken down by quarter.">
<caption>Widget sales figures, 2007</caption>
<tr>
<th scope="col">Product</th>
<th scope="col">1st quarter</th>
<th scope="col">2nd quarter</th>
<th scope="col">3rd quarter</th>
<th scope="col">4th quarter</th>
</tr>
<tr>
<th scope="row">SupaWidget</th>
<td>$9,940</td>
<td>$10,100</td>
<td>$9,490</td>
<td>$11,730</td>
</tr>
<tr class="alt">
<th scope="row">WonderWidget</th>
<td>$19,310</td>
<td>$21,140</td>
<td>$20,560</td>
<td>$22,590</td>
</tr>
<tr>
<th scope="row">MegaWidget</th>
<td>$25,110</td>
<td>$26,260</td>
<td>$25,210</td>
<td>$28,370</td>
</tr>
<tr class="alt">
<th scope="row">HyperWidget</th>
<td>$27,650</td>
<td>$24,550</td>
<td>$30,040</td>
<td>$31,980</td>
</tr>
</table>



...and the corresponding CSS:





.pretty-table
{
padding: 0;
margin: 0;
border-collapse: collapse;
border: 1px solid #333;
font-family: "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
font-size: 0.9em;
color: #000;
background: #bcd0e4 url("widget-table-bg.jpg") top left repeat-x;
}

.pretty-table caption
{
caption-side: bottom;
font-size: 0.9em;
font-style: italic;
text-align: right;
padding: 0.5em 0;
}

.pretty-table th, .pretty-table td
{
border: 1px dotted #666;
padding: 0.5em;
text-align: left;
color: #632a39;
}

.pretty-table th[scope=col]
{
color: #000;
background-color: #8fadcc;
text-transform: uppercase;
font-size: 0.9em;
border-bottom: 2px solid #333;
border-right: 2px solid #333;
}

.pretty-table th+th[scope=col]
{
color: #fff;
background-color: #7d98b3;
border-right: 1px dotted #666;
}

.pretty-table th[scope=row]
{
background-color: #b8cfe5;
border-right: 2px solid #333;
}

.pretty-table tr.alt th, .pretty-table tr.alt td
{
color: #2a4763;
}

.pretty-table tr:hover th[scope=row], .pretty-table tr:hover td
{
background-color: #632a2a;
color: #fff;
}



Internet Explorer 6 notes



If you're viewing the example table in IE6, you're probably wondering what all the fuss is about. Although the table looks decent enough in IE6, it's missing some features:





  • The header cells are the same style as the data cells. This is because IE6 doesn't understand attribute selectors or adjacent sibling selectors. To get the same effect in IE6, add classes to the topmost table row, the leftmost table column, and the "Product" header cell. You can then select these cells using classes, rather than attribute and adjacent sibling selectors. (It's messy markup though, and I wanted to keep the markup clean for this example!)




  • The table caption is along the top of the table. IE6 doesn't recognize the caption-side property, so there's not a lot you can do about this!




  • The hover effect doesn't work. Sadly, IE6 only recognizes the :hover pseudo-class when applied to links; you can't use it with any other element types. Possible workarounds — if you're desperate to have the hover in IE6 — include wrapping a link around the text in each table cell (using display:block to ensure the link fills the cell); using JavaScript's onmouseover/onmouseout events to achieve the hover effect; or using this clever behaviour trick to allow :hover to work with any element type in IE6.


No comments: