← Back to HTML Course | Chapter 2: Text, Formatting & Media | Lesson 19 of 26

HTML Table Headers

A table full of raw numbers is meaningless without labels explaining what each row and column represents. The <th> (table header) element marks a cell as a header rather than plain data, which browsers render as bold and centered by default, and which screen readers use to announce column and row labels automatically as a user navigates through the data.

The Basic th Element

<th> works exactly like <td> structurally -- it is a table cell -- but it carries the semantic meaning of "this cell is a label," which browsers render in bold, centered text by default, and which assistive technology announces distinctly from ordinary data cells.

Note: Use <th> for every cell that labels a row or column, and reserve <td> strictly for the actual data values themselves.

Warning: A <td> styled to look bold and centered with CSS still lacks the semantic header meaning that <th> provides to screen readers.

Example: The Basic th Element

markup
<table>
  <tr>
    <th>Name</th>
    <td>Alex</td>
  </tr>
</table>

Clarifying Direction with the scope Attribute

The scope attribute on a <th> element explicitly states whether it labels an entire column (scope="col") or an entire row (scope="row"), removing any ambiguity for screen reader users navigating a table with headers on both axes at once.

Note: Always add scope to header cells in any table that has both a header row and a header column, since that is when direction becomes genuinely ambiguous.

Warning: Skipping scope on a table with two-directional headers can make screen reader announcements confusing or ambiguous for users relying on them.

Example: Clarifying Direction with the scope Attribute

markup
<table>
  <tr>
    <th scope="col">Name</th>
    <th scope="col">Age</th>
  </tr>
</table>

Grouping with thead

<thead> wraps the header row(s) of a table in a distinct semantic section, separate from the actual data rows. This grouping is useful both for styling the header differently (like a different background color) and for browsers that support repeating the header row when a long table is printed across multiple pages.

Note: Wrap your header row in <thead> even on small tables, as a consistent habit that pays off the moment a table grows large.

Warning: A table can technically render fine without <thead>, but omitting it loses the semantic and print-repeating benefits for larger tables.

Example: Grouping with thead

markup
<table>
  <thead>
    <tr><th>Name</th></tr>
  </thead>
</table>

Grouping with tbody and tfoot

<tbody> wraps the main data rows of a table, while <tfoot> wraps a footer row, commonly used for totals or summary values. Together with <thead>, these three sections divide a table into clearly labeled zones, each independently stylable and, in some layouts, independently scrollable.

Note: Place <tfoot> for a totals row even though it appears visually at the bottom, since some browsers render it at the bottom of the table regardless of its position in the markup.

Warning: A table with many rows but no <tbody> wrapper misses out on the ability to independently scroll or style just the body rows.

Example: Grouping with tbody and tfoot

markup
<table>
  <tbody>
    <tr><td>Apple</td></tr>
  </tbody>
  <tfoot>
    <tr><td>Total</td></tr>
  </tfoot>
</table>

Accessible Multi-Level Headers with headers and id

For complex tables where a single data cell relates to more than one header (like both a row label and a column label), the id attribute on each <th> combined with the headers attribute on a <td> creates an explicit link, letting screen readers announce every relevant header for that specific cell.

Note: Reserve the id/headers technique for genuinely complex tables; simple tables are usually clear enough with scope alone.

Warning: Typos in a headers attribute value that do not match any existing th id will silently fail to associate, with no visible error.

Example: Accessible Multi-Level Headers with headers and id

markup
<table>
  <tr>
    <th id="name">Name</th>
  </tr>
  <tr>
    <td headers="name">Alex</td>
  </tr>
</table>
Common Mistakes
  1. Using a bold, styled <td> instead of a real <th> element for header cells, which looks the same visually but loses the semantic meaning screen readers rely on.
  2. Forgetting the scope attribute on header cells in complex tables, making it ambiguous whether a header applies to its row or its column.
  3. Placing header cells outside of a <thead> section, which loses the semantic grouping benefit for large tables with many data rows.
Chapter Summary
  • <th> marks a cell as a header, rendering bold and centered by default and carrying semantic meaning for screen readers.
  • The scope attribute (scope="col" or scope="row") clarifies whether a header describes an entire column or an entire row.
  • <thead>, <tbody>, and <tfoot> group header rows, body rows, and footer rows semantically and enable independent styling and scrolling.
Browser Support

<th>, <thead>, <tbody>, <tfoot>, and the scope attribute are supported in every browser and have been part of HTML since HTML4.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.