HTML5 Fundamentals

Tables: Structure, Spanning & Accessibility

30 min Lesson 9 of 18

Why Tables Exist

HTML tables were designed for one purpose: displaying tabular data -- information that naturally belongs in rows and columns, such as schedules, statistics, price comparisons, and spreadsheets. Before CSS layout tools like Flexbox and Grid existed, developers misused tables to build entire page layouts. That practice is now obsolete and harmful to accessibility. In this lesson, you will learn how to build well-structured, accessible tables the right way.

Basic Table Structure

Every table starts with the <table> element. Inside it, you create rows with <tr> (table row), header cells with <th> (table header), and data cells with <td> (table data). Header cells are bold and centered by default, signaling to both browsers and screen readers that they label the data in that column or row.

Example: A Simple Product Table

<table>
  <tr>
    <th>Product</th>
    <th>Price</th>
    <th>In Stock</th>
  </tr>
  <tr>
    <td>Keyboard</td>
    <td>$45.00</td>
    <td>Yes</td>
  </tr>
  <tr>
    <td>Mouse</td>
    <td>$25.00</td>
    <td>No</td>
  </tr>
</table>

Semantic Sections: thead, tbody, tfoot

For larger tables, you should group rows into logical sections using <thead> for the header row(s), <tbody> for the main data, and <tfoot> for summary or total rows. These elements do not change the visual appearance by default, but they provide meaningful structure for screen readers, enable independent scrolling of the body in some browsers, and make your CSS selectors more precise.

Example: Table with Semantic Sections and Caption

<table>
  <caption>Q3 Sales Report</caption>
  <thead>
    <tr>
      <th scope="col">Month</th>
      <th scope="col">Units Sold</th>
      <th scope="col">Revenue</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>July</td>
      <td>1,200</td>
      <td>$36,000</td>
    </tr>
    <tr>
      <td>August</td>
      <td>1,450</td>
      <td>$43,500</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td>Total</td>
      <td>2,650</td>
      <td>$79,500</td>
    </tr>
  </tfoot>
</table>

The <caption> element provides a title for the table. It must be the first child inside <table>. Screen readers announce the caption before reading the table content, giving users context about what data they are about to hear.

Spanning Rows and Columns

Sometimes a cell needs to stretch across multiple columns or rows. Use the colspan attribute to span columns and rowspan to span rows. When you use these attributes, make sure the total number of cells in each row still adds up correctly -- a cell with colspan="2" replaces two normal cells.

Example: Using colspan and rowspan

<table>
  <thead>
    <tr>
      <th rowspan="2" scope="col">Employee</th>
      <th colspan="2" scope="colgroup">Contact</th>
    </tr>
    <tr>
      <th scope="col">Email</th>
      <th scope="col">Phone</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sara Ahmed</td>
      <td>sara@example.com</td>
      <td>555-0101</td>
    </tr>
    <tr>
      <td>Omar Ali</td>
      <td>omar@example.com</td>
      <td>555-0202</td>
    </tr>
  </tbody>
</table>

Accessibility with the scope Attribute

The scope attribute on <th> elements tells screen readers whether a header applies to a column (scope="col"), a row (scope="row"), or a group of columns (scope="colgroup"). Without scope, screen readers must guess which data cells a header describes, often getting it wrong in complex tables. Always add scope to your table headers.

Styling Tables with CSS

By default, HTML tables have no borders and look plain. All visual styling should be done through CSS, not through deprecated HTML attributes like border, cellpadding, or bgcolor. A common pattern is zebra striping, where alternating rows have different background colors to improve readability.

Example: CSS Styling with Zebra Stripes

<style>
  table {
    width: 100%;
    border-collapse: collapse;
    font-family: Arial, sans-serif;
  }
  th, td {
    border: 1px solid #ddd;
    padding: 10px 14px;
    text-align: left;
  }
  th {
    background-color: #2c3e50;
    color: white;
  }
  tbody tr:nth-child(even) {
    background-color: #f2f2f2;
  }
  tbody tr:hover {
    background-color: #e8e8e8;
  }
</style>

The border-collapse: collapse property merges adjacent cell borders into a single border, which looks much cleaner than the default double-border style.

Responsive Tables

Wide tables can overflow on small screens. A simple responsive strategy is to wrap the table in a scrollable container. This keeps the table intact while allowing horizontal scrolling on mobile devices:

Example: Responsive Table Wrapper

<div style="overflow-x: auto;">
  <table>
    <!-- table content here -->
  </table>
</div>

When NOT to Use Tables

Tables must only be used for tabular data. Never use tables for page layout, navigation menus, aligning images, or creating multi-column text. Layout tables confuse screen readers, which try to read them as data, and they break on mobile devices. Use CSS Flexbox or Grid for layout instead. A good rule of thumb: if the data would make sense in a spreadsheet, use a table. If not, use CSS layout.

Note: The <caption> element is the only proper way to give a table a title. Do not use a heading tag above the table as a substitute -- screen readers associate <caption> directly with the table, but a heading tag is read as a separate element.
Pro Tip: Use border-collapse: collapse in your CSS as the very first table style. Without it, cells have double borders with gaps between them, which is almost never the design you want. This single property solves most table styling frustrations.
Common Mistake: Using tables for page layout. If you find yourself creating a table with a single row and multiple cells just to place elements side by side, stop and use CSS Flexbox or Grid instead. Layout tables are a relic of the 1990s and cause serious accessibility problems.

Practice Exercise

Build a class schedule table that displays five days of the week across the top (as column headers) and four time slots down the left side (as row headers with scope="row"). Include a <caption> that reads "Weekly Class Schedule." Make at least one class span two time slots using rowspan="2". Wrap the table in <thead>, <tbody>, and <tfoot>, and add a footer row with colspan that displays the total number of classes. Finally, add CSS zebra striping to alternate row colors.

ES
Edrees Salih
16 hours ago

We are still cooking the magic in the way!