Picture of the author
Visit my website
Published on
ยท
Reading time
6 min read

Can You Colour This Table?

A problem-solving approach to re-skinning a table using CSS

Share this page

Image of a beige stool with an overlay of bright colours
Image source: Beige stool and bright colours from Unsplash.

Introduction

I recently came across a unique problem whose solution relied solely on CSS. I thought I must document it and explain the process of coming up with a solution for those intrigued.

Problem

I'm going to recreate the scenario โ€” I need to re-skin the tables on a website and the given requirements tell me that the background colour of the table header must be yellow, and the table rows will be the alternating background colour of wheat and plum. Below is a picture of what the table can look like.

Image showing the accepted outputs for the tables

Accepted outputs for the tables

I have two tables on the website, however, the HTML structure of both the tables is different. One makes use of a <thead> and <tbody> element to distinguish the table's header from the body. The HTML looks something like the snippet below.

<table border="1">
  <thead>
    <tr>
      <th>Heading 1</th>
      <th>Heading 2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Lorem ipsum 1</td>
      <td>Lorem ipsum 2</td>
    </tr>
    <tr>
      <td>Lorem ipsum 3</td>
      <td>Lorem ipsum 4</td>
    </tr>
    <tr>
      <td>Lorem ipsum 5</td>
      <td>Lorem ipsum 6</td>
    </tr>
    <tr>
      <td>Lorem ipsum 7</td>
      <td>Lorem ipsum 8</td>
    </tr>
  </tbody>
</table>

Table with a <thead> and a <tbody>

While the other table only uses a<tbody> element like the snippet below.

<table border="1">
  <tbody>
    <tr>
      <th>Heading 1</th>
      <th>Heading 2</th>
    </tr>
    <tr>
      <td>Lorem ipsum 1</td>
      <td>Lorem ipsum 2</td>
    </tr>
    <tr>
      <td>Lorem ipsum 3</td>
      <td>Lorem ipsum 4</td>
    </tr>
    <tr>
      <td>Lorem ipsum 5</td>
      <td>Lorem ipsum 6</td>
    </tr>
    <tr>
      <td>Lorem ipsum 7</td>
      <td>Lorem ipsum 8</td>
    </tr>
  </tbody>
</table>

Table with only a <tbody>

The challenge is:

  • I must not update the HTML code for either of these tables.
  • I must use the same CSS solution for both the tables.

Follow along (optional)

If you'd like to follow along and learn, feel free to fork this JSFiddle for the table with <thead> and <tbody> and this JSFiddle for the table with only a <tbody>. Here's how you would fork and create your copy of a JSFiddle.

Image showing how to fork a fiddle

Forking a JSFiddle creates your copy of the original

Solution

Alright, let's solve this problem one step at a time.

Alternating background colours

First, we need alternating background colours for our table rows, so let's go ahead and add the following CSS rules. This will give a background colour of wheat to all even rows and background colour of plum to all odd rows.

tr:nth-child(even) {
  background: wheat;
}
tr:nth-child(odd) {
  background: plum;
}

This produces the following output for both tables.

Image showing both tables after adding alternate background colour

Both tables after adding an alternate background colour

You might have noticed that the left table has a plum colour background on rows one and two, but earlier I mentioned that only odd rows will get a background of plum and even row will get background colour of wheat. This is because the nth-child rule works for elements under the same parent. Since the heading row in the left table is under a <thead> element, and the first lorem ipsum row in the body is under the <tbody> element, both qualify to be an odd-numbered row as both these table rows are row #1 under their parent elements.

Cool. Now that we've got our alternate colours sorted, we need to make the table header yellow.

Yellow table header: Round 1

Since we need the first row in both tables to be yellow, let's add this CSS rule to our CSS file.

tbody tr:first-of-type,
thead tr:first-of-type {
  background-color: yellow;
}

The selector tbody tr:first-of-type targets the first <tr> element nested inside a <tbody> element. And the selector thead tr:first-of-type targets the first <tr> element inside a <thead> element. This produces the below output.

Image showing both tables after round 1 of adding a yellow table header

Both tables after round 1 of adding a yellow table header

The table with only a <tbody> element satisfies our expected outcome, but the table with a <thead> and <tbody> doesn't look right. What happened there was, both the selectors tbody tr:first-of-type and thead tr:first-of-type got applied since this table has both a <thead> and a <tbody> element and a <tr> nested inside both.

Yellow table header: Round 2

Now we need to fix our left table without disturbing the right table. What's your first thought?

What?

I see.

Okay, let's try that.

You're thinking that we should just add the below CSS rule at the end of the CSS file so that this takes precedence over the code we've written above. This will change the background colour of the first <tr> that's nested only inside <tbody> to plum.

tbody tr:first-of-type {
  background-color: plum;
}

And indeed, it did change. This is what both the tables look like now. ๐Ÿ˜…

Image showing both tables after round 2 of adding a yellow table header

Both tables after round 2 of adding a yellow table header

We've managed to fix the left table, but disturbed the output of the right table. Don't be disheartened. We're on the right track to solving this.

Yellow table header: Round 3

Let's prepend the thead + to the above CSS selector. It should now look something like this.

thead + tbody tr:first-of-type {
  background-color: plum;
}

The + sign in a CSS selector is used to represent an adjacent sibling. This means the <thead> and <tbody> elements must be adjacent elements in the produced HTML, and <tbody> must appear immediately after <thead> and not before. The first <tr> element under such a <tbody> element will get this CSS rule applied.

This produces the following output. Hey, this is what we expected our outcome to look like, didn't we? Great work! ๐Ÿ˜„

Image showing both tables after round 3 of adding a yellow table header

Both tables after round 3 of adding a yellow table header

If you'd like to see the final results for both variations of the tables and maybe even experiment a bit by changing a few things, feel free to fork the following JSFiddles.

That's it! Thanks for reading.