Eric Niquette UI, UX, Accessibility

Writing and remediating accessible tables in HTML documents

Published Updated

When leveraged correctly, tables are an efficient way to provide large amounts of data in a user-friendly format. When structured incorrectly however, tables can be a significant challenge to users relying on assistive tools.

What makes a table accessible

For a table to be considered accessible, it should consist of linear data that titled by header cells, and ideally include a caption. Supplementary features such as colour banding, summaries, captions, and other explanatory notes can also be used to further enhance a table's accessibility.

Conversely, a table that is lacking structure, is marked up incorrectly, or otherwise has a non-linear reading order is considered an accessibility barrier and would be significantly difficult to parse using assistive tools such as screen readers.

How screen readers parse tables

When navigating to a table, screen readers provide the user basic information on the table's structure, such as the number of rows and columns it contains as well as the table's caption and summary, if present.

Sample table
Table 1. Prevalence of disability in Canadians by age group
Age Prevalence of disability
15 to 24 years 13.1%
25 to 44 years 15.3%
45 to 64 years 24.3%
65 to 74 years 32.0%
75 years and over 47.4%

The following is an example of how the desktop screen reader, NVDA, parses a simple table. If this is the first time you hear a screen reader, note that the speech rate was lowered for this recording; typical users configure them to a dramatically faster speech rate.

As the screen reader moves through cells, it announces the cell's header before its contents. This highlights the importance of keeping header cells and their related data linear. In other words, data cells shouldn't be merged, split, or otherwise left empty as doing so would break the linear order.

Layout tables

A layout table is used for design. The goal is typically to create a grid-like arrangement rather than use the table to present actual data. The practice is commonly-seen in word processors and on old legacy websites, before the advent of CSS. As a design tool, tables provide an easy way to align content to a grid; cells can be split, merged, or resized to suit most needs.

While modern screen readers have become adepts at distinguishing between data and layout tables, the preferred approach is to utilize modern, accessible CSS techniques such as the grid and flex layouts, or using floats where appropriate.

Accessibility impacts of layout tables

When a table is used to layout a design, the table's reading order may not reflect the code's order. This can cause the reader to move through the content in an erratic way that doesn't follow the on-screen flow.

In the following example, notice how a screen reader would parse the table and how the order differs from the content's natural flow.

Illustration of a layout table with an erratic reading order. The reading order jumps from the header, to the menu, main content, unrelated sidebar items, back to the table of contents, and finally to another sidebar item.

Layout tables can also be misidentified as data tables, which would cause screen readers to announce every cell as the user moves through the contents, potentially causing confusion and making navigation difficult.

By design, tables also retain their layout when resized; you wouldn't want your cells to shift around and break the intended order. This also means that tables don't reflow their layout to fit the viewport; they can only compress or expand. When a table requires a larger viewport than is available, the contents will break into multiple lines or cause horizontal scrolling.

Minimizing the impact of layout tables in HTML

When a screen reader inspects a table with the goal of determining whether its used to present data or for layout, it does so with a series of checks and by searching for certain giveaways like header cells, captions, and summaries.

If you must use a layout table, ensure that you are not providing any supporting features that could potentially trigger the interpretation as data. Convert any <th> cells to <td>, and remove both the <caption> element and summary attribute.

Tables can also be assigned a role of presentation, which strips it of its semantic value. In other words, it treats the table as if it were non-semantic element, such as an empty container.

<table role="presentation">
      <h1>My presentation table</h1>


Table header cells are an important element to visual and non-visual users alike as they provide context on the type of content a cell contains. For example, the column header cell titled “E-mail address” informs the user to expect data formatted as an e-mail address in its associated cells.

Tables can contain a header row, a header column, or both.

Table headers in HTML

The <th> element is used to indicate a header, rather than a <td> data cell. The scope attribute defines whether the cell applies to the row, column, or both.

      <th scope="col">Course</th>
      <th scope="col">Professor</th>
      <th scope="col">Day</th>
      <th scope="col">Time</th>
      <th scope="col">Room</th>
      <th scope="row">English</th>
      <td>John Doe</td>
      <td>11:20 am</td>

Headers in complex tables

A table is generally deemed complex when its data is not linear, typically due to multiple rows of headers, or when cells have been split or merged together. In some cases, tables may also be nested in another table.

As a general rule, and one I enforce very strongly, complex tables should be avoided and instead broken down into smaller linear tables. In cases where this may not be possible, header cells will have to be manually associated to their respective data cells. This can be a grueling task, particularly if you lack the supporting tools.

To assign header cells, each header is provided an ID. This is then, with the headers attribute, associated to data cells.

      <th colspan="2" id="q1">First quarter</th>
      <th colspan="2" id="q2">Second quarter</th>
      <th id="in_q1" headers="q1">Income</th>
      <th id="out_q2" headers="q1">Expenses</th>
      <th id="in_q2" headers="q2">Income</th>
      <th id="out_q2" headers="q2">Expenses</th>
      <td headers="q1 in_q1">5.6</td>
      <td headers="q1 out_q1">4.2</td>
      <td headers="q2 in_q2">4.4</td>
      <td headers="q2 out_q2">4.1</td>

Empty cells and state indicators

In general, care should be taken to avoid leaving empty cells in a data table. Some screen readers will opt to skip empty cells, breaking the linear flow of data. Instead, provide useful information to the user. If the cells contains no value, opt for a text value of "0" or "Null".

It should be noted that some screen readers will also fail to announce cells that only contain a dash.

Avoid populating a cell to indicate a state. Using a cell as a makeshift checkbox or using "X" is not descriptive to users relying on screen readers and assistive devices. Use explicit, descriptive text such "Pass", "Included" to provide a state or condition.


A table caption is a short, introductory title that appears before the table and provides context or a brief description of the contents found within. Table captions often include a reference number such as "Table 1", though this is not required.

Adding a caption

In HTML, the caption element must be introduced immediately following the table's opening tag.

  <caption>Table 1. My table caption</caption>
  <thead> [...]

Notes and explanatory text

If your table contains an explanatory note, it should be found in the caption as it is typically provided before the table itself in order to provide context to the user.

  <caption>Table 1. My table caption<br>
  Includes partial financial information for the second quarter of 2023</caption>
  <thead> [...]

Colour and design considerations

Colour is a great way to visually highlight key points of data, but care should be taken that it is not solely used to provide information or context. Some users may find it difficult to differentiate between certain hues, or see colours differently. Solely relying on Using green and red to indicate a passing or failure state could be problematic to users with deuteranomaly .

To facilitate scanning, the use of colour-banded rows or columns is a practical enhancement as long as the chosen background colours provide sufficient contrast with the text.

Structure elements

Tables can be structured in three content sections: the head, body, and footer.

The head of a table normally consists of header rows and is marked with the <thead> element. The body, where the data cells reside, is indicated with the <tbody> element. Finally, the footer is where you'll typically find elements such as totals and calculations. The footer is marked with the <tfoot> element.

  <caption>2022-2023 Financial Summary</caption>
      <th scope="col">Year</th>
      <th scope="col">Income</th>
      <th scope="col">Expenses</th>
      <th scope="col">Value</th>
      <th scope="row">2022<td>
      <th scope="row">2023<td>
      <th scope="row">Total<td>

These elements do not impact accessibility and are primarily used for styling tables and in printing. While it is generally good practice to include them, they can be omitted with little to no impact in most cases.

Other resources

Using id and headers attributes to associate data cells with header cells in data tables

World Wide Web Consortium (W3C)

HTML table advanced features and accessibility

Mozilla Developer Network