Web Development
HTML Course
CSS Course
JavaScript Course
PHP Course
Python Course
SQL Course
SEO Course

HTML Tables

A very common task in HTML is structuring tabular data, and there are a number of elements and attributes designed specifically for this purpose. Along with a bit of CSS for styling, HTML allows you to display tables with information, such as a tenant list or the schedule at the local cinema. In this section, we'll discuss everything you need to know about structuring tabular data with HTML.

Basic Elements of HTML Tables

A table is a structured set of data made up of rows and columns (tabular data). A table allows you to quickly and easily look up values that indicate some kind of connection between different types of data — for example, the type of a course, its duration, and its price:

🔧 HTML table example

When tables are properly built, even blind users can interpret tabular data from an HTML table.

For tables to be effective on the web, we need to provide some styling information using CSS, as well as a solid structure using HTML. In this module, we focus on the HTML part.

When Should You Not Use HTML Tables?

HTML tables should be used for tabular data — that's what they're designed for. Unfortunately, many developers used to rely on HTML tables to build web pages, for example a row for the header, a row containing content columns, a row for the site footer, etc. This method was commonly used because CSS support in browsers used to be terrible many years ago.

In short, using tables to build a website instead of CSS layout techniques is a bad idea. The main reasons are as follows:

Creating a Table

Let's build a simple table together.

The content of every table is enclosed between these two tags: <table>...</table>.

The smallest container inside a table is a table cell, which is created using the <td> element (td stands for “table data”). Create a file named tabel.html and add the following lines between the <body>...</body> tags:

<table>
  <td>Cell 1</td>
</table>

If we want a row with four cells, we need to copy the first cell three more times. Update your table content to look like this:

<table>
  <td>Cell 1</td>
  <td>Cell 2</td>
  <td>Cell 3</td>
  <td>Cell 4</td>
</table>

As you'll see, the cells are not placed one below the other, but automatically align side by side in the same row. Each td element creates a single cell, and together they form the first row. Every cell you add makes the row grow longer.

To stop this row from growing and start placing subsequent cells on a second row, we need to use the <tr> element (tr stands for “table row”). Let's update the table by wrapping the four cells we already created inside tr tags, like this:

<table>
  <tr>
    <td>Cell 1</td>
    <td>Cell 2</td>
    <td>Cell 3</td>
    <td>Cell 4</td>
  </tr>
</table>

Now let's create another row — each row must be wrapped in an additional tr element, with each cell enclosed in a td tag:

<table>
  <tr>
    <td>Row 1-Cell 1</td>
    <td>Row 1-Cell 2</td>
    <td>Row 1-Cell 3</td>
    <td>Row 1-Cell 4</td>
  </tr>
  <tr>
    <td>Row 2-Cell 1</td>
    <td>Row 2-Cell 2</td>
    <td>Row 2-Cell 3</td>
    <td>Row 2-Cell 4</td>
  </tr>
</table>

As mentioned earlier, we need minimal styling using CSS to make our table look better.

Copy and paste the code below into the head section of your tabel.html file:

<style>
  html {
    font-family: sans-serif;
  }

  table {
    border-collapse: collapse;
    border: 2px solid rgb(200,200,200);
    letter-spacing: 1px;
    font-size: 0.8rem;
  }

  td, th {
    border: 1px solid rgb(190,190,190);
    padding: 10px 20px;
  }

  th {
    background-color: rgb(235,235,235);
  }

  td {
    text-align: center;
  }

  caption {
    padding: 10px;
  }
</style>

Our table should now look like the one in the following example:

🔧 Simple Table Example

Adding Headers Using the <th> Element

Now let's turn our attention to table headers — special cells that go at the beginning of a row or column and define the type of data that row or column contains.

To mark table headers both visually and semantically, we use the <th> element (th stands for “table header”). It works similarly to the td element, except it generates a header cell instead of a regular one.

Headers are important because they make it easier to find the data we're looking for, and the design generally looks better.

Let's update our table like this:

<table>
   <tr>
    <td>&nbsp;</td>
    <th>Vertical Header</th>
    <th>Vertical Header</th>
    <th>Vertical Header</th>
    <th>Vertical Header</th>
  </tr>
  <tr>
    <th>Horizontal Header</th>
    <td>Row 1-Cell 1</td>
    <td>Row 1-Cell 2</td>
    <td>Row 1-Cell 3</td>
    <td>Row 1-Cell 4</td>
  </tr>
  <tr>
    <th>Horizontal Header</th>
    <td>Row 2-Cell 1</td>
    <td>Row 2-Cell 2</td>
    <td>Row 2-Cell 3</td>
    <td>Row 2-Cell 4</td>
  </tr>
</table>

Our table should look like the one below:

🔧 Table Example

Expanding Cells Across Multiple Rows and Columns

Sometimes we want cells to span across multiple rows or columns.

To do this, we use the colspan and rowspan attributes. Both accept unitless values. For example, colspan = "2" makes a cell span two columns.

Returning to our table, we'll remove one horizontal header and two vertical headers so the remaining ones can expand:

<table>
   <tr>
    <td>&nbsp;</td>
    <th colspan="2">Vertical Header</th>
    <th colspan="2">Vertical Header</th>
  </tr>
  <tr>
    <th rowspan="2">Horizontal Header</th>
    <td>Row 1-Cell 1</td>
    <td>Row 1-Cell 2</td>
    <td>Row 1-Cell 3</td>
    <td>Row 1-Cell 4</td>
  </tr>
  <tr>
    <td>Row 2-Cell 1</td>
    <td>Row 2-Cell 2</td>
    <td>Row 2-Cell 3</td>
    <td>Row 2-Cell 4</td>
  </tr>
</table>

Now, our table looks like this:

🔧 Table Example

One last feature we'll discuss in this article before moving on

HTML has a method for defining styling information for an entire column of data — the <col> and <colgroup> elements. These exist because it can be a bit annoying and inefficient to specify styles for columns — generally, you have to apply styling to every td or th in the column or use a complex selector like nth-child().

Now, I'm going to color the first two cells of each vertical header (column) differently. To do this, I'll modify the table and add the following markup right after the opening table tag, like so:

<table>
  <colgroup>
    <col>
    <col style="background-color: yellow;">
  </colgroup>
  <colgroup>
    <col>
    <col style="background-color: skyblue;">
  </colgroup>

  <tr>
    <td>&nbsp;</td>
    <th colspan="2">Vertical Header</th>
    <th colspan="2">Vertical Header</th>
  </tr>
  <tr>
    <th rowspan="2">Horizontal Header</th>
    <td>Row 1-Cell 1</td>
    <td>Row 1-Cell 2</td>
    <td>Row 1-Cell 3</td>
    <td>Row 1-Cell 4</td>
  </tr>
  <tr>
    <td>Row 2-Cell 1</td>
    <td>Row 2-Cell 2</td>
    <td>Row 2-Cell 3</td>
    <td>Row 2-Cell 4</td>
  </tr>
</table>

After these changes, our table looks like this:

🔧 Table Example

We can specify styling information just once on a col element. col elements are placed inside a colgroup container, right below the opening table tag.

Effectively, we define two “style columns,” each specifying styling information for a column. We don't apply styling to the first column, but we still need to include an empty col element — if we don't, the style will only be applied to the first column.

If we wanted to apply styling information to both columns, we could include just one col element with a span attribute, like this:

<colgroup>
  <col style="background-color: yellow" span="2">
</colgroup>

Just like rowspan and colspan, span takes a unitless value that specifies the number of columns the style should apply to.

HTML Tables – Advanced Features and Accessibility

Let's now look at some more advanced features of HTML tables — such as captions, summaries, and grouping rows into head, body, and footer sections — as well as table accessibility for users with visual impairments.

You can give your table a title or description by placing it inside a <caption> element just below the opening <table> tag:

<table>
  <caption>My table description</caption>
    ...
</table>

As you can tell from the example above, caption serves to provide a brief description of the table's content. This is helpful for all readers who want to quickly understand whether the table is useful to them as they scan the page — but especially for blind users. Instead of reading the entire table, they can read the caption to find out what the table is about and then decide whether to read it in more detail.

As the table becomes more complex in structure, it's helpful to use a more structural definition. We can do this using <thead>, <tfoot>, and <tbody>, which allow you to mark a header, a footer, and a section for the body of the table.

These elements do not make the table more accessible for screen reader users and do not result in visual improvements. However, they are very useful for the design and layout of the table, because they can be targeted with CSS.

These structural elements are used as follows:

tbody is always included in every table by default, even if you don't add it in your code. To verify this, open the example above and inspect the HTML code using your browser's developer tools — you'll see that the browser has added this tag. It's a good idea to include it manually, because it gives you more control over the table's structure and styling.

Before we move on, let's take a look at how our table looks now, after adding the structural elements we just discussed:

🔧 Finalized Simple Table Example

Nested Tables

It is possible to insert a table inside another table, as long as the full structure is included — including the table element itself. In general, this is not recommended because it makes the markup more confusing and less accessible for screen reader users. In many cases, you can simply insert additional cells / rows / columns into the existing table instead.

In the example below, you can see a nested table:

🔧 Nested Tables Example

Tables for Users with Visual Impairments

Reading plain text is not a problem, but interpreting a table can be a challenge for people with visual impairments. However, with proper markup, we can replace visual associations with programmatic ones.

Using Column and Row Headers

Screen readers will identify all headers (th) and use them to create programmatic associations between those headers and the cells they refer to. The combination of column and row headers will help identify and interpret the data in each cell, allowing screen reader users to understand the table similarly to how a sighted user would.

The scope Attribute

This attribute can be added to the th element to tell screen readers exactly which cells a particular header applies to — whether it's a header for the row it's in or for the column. This way, we can clearly define column headers like this:

<thead>
  <tr>
    <th scope="col">Date</th>
    <th scope="col">Location</th>
    <th scope="col">Cost</th>
  </tr>
</thead>

And each row could have a header defined like this (if we add row headers as well as column headers):

<tr>
  <th scope="row">Haircut</th>
  <td>12/09</td>
  <td>21</td>
</tr>

Screen readers will recognize this structured markup and allow users to read the entire column or row.

The scope Attribute Has Two Other Possible Values — colgroup and rowgroup

These are used for headers that span across multiple columns or rows. If we look at the table below, you'll see that the “Duration” cell sits above the “Day” and “Evening” cells. All of these cells should be marked as headers (th), but “Duration” is a title that sits above and defines the other two subpositions. Therefore, “Duration” should receive a colgroup scope attribute, while the others should use col. Likewise, “HTML/CSS” should be marked with a rowgroup attribute.

🔧 Table for Users with Visual Impairments

🧠 Quiz – HTML: Tables and Data Structuring

Top