Data tables

Data tables with large data sets have some specific accessibility requirements.

How will a screen-reader user know the table is sortable?

Tables should have captions and we want to hook into that element to tell the user that the table is sortable.

<table>
  <caption>
    Past bookings<span class="visually-hidden"
      >, any headings with buttons are sortable</span
    >
  </caption>
</table>

This will now read the semantics of the table caption to the screen-reader.

Also, in this caption, we are telling the user that the buttons in the table headings are sortable buttons.

Sortable columns

Columns that are sortable need to inform screen-readers of that. We've already informed the user via the caption tag but now we're going to add buttons and aria-sort values to the th (table headings).

<thead>
  <tr>
    <th aria-sort="descending" scope="col">
      <button
        aria-pressed="true"
        type="button"
        onclick="javascript:handleTableSort(event)"
      >
        Date
        <span class="icon" aria-hidden="true"></span>
      </button>
    </th>
    <th scope="col">Reference</th>
    <th scope="col">
      <button type="button" onclick="javascript:handleTableSort(event)">
        Passenger
        <span class="icon" aria-hidden="true"></span>
      </button>
    </th>
    <th scope="col">Journey</th>
  </tr>
</thead>

The th have an aria-sort value of either descending, ascending or nothing at all.

The button inside the th has an aria-pressed attribute with a boolean value.

The icon inside the button gives a visual clue that the column is a: sortable and b: currently sorted is hidden from the screen-reader using aria-hidden="true" (because aria-sort should do that work for the screen-reader user, along with the caption's message).

How does a screen-reader user know the table has been sorted?

If the table is sortable want an aria-live region in the DOM that will tell the user when they have initiated a sorting of the table. Otherwise they may not know.

Note: the aria-live region must be empty on first load/render. We don't want the screen-reader to read out its contents until a user interacts by clicking a table heading… so until that happens we want this element's innerText to equal null.

On load/first render

<span aria-live="polite" class="visually-hidden" id="feedback"></span>

After an interaction

<span aria-live="polite" class="visually-hidden" id="feedback">
  The past bookings table is now sorted by date in descending order
</span>

This lets the user know what has happened (the table is now sorted by date in descending order) and to what (the past bookings table).

Full code example

<div class="table=-container">
  <span aria-live="polite" class="visually-hidden" id="feedback"></span>
  <table>
    <caption>Past bookings <span class="visually-hidden">, any headings with buttons are sortable</span>
    <thead>
      <tr>
        <th aria-sort="descending" scope="col">
          <button aria-pressed="true" type="button" onclick="javascript:handleTableSort(event)">
            Date
            <span class="icon" aria-hidden="true"></span>
          </button>
        </th>
        <th scope="col">Reference</th>
        <th scope="col">
          <button type="button" onclick="javascript:handleTableSort(event)">
            Passenger
          <span class="icon" aria-hidden="true"></span>
          </button>
        </th>
        <th scope="col">Journey</th>
      </tr>
    </thead>
    <tbody>
      <tr onClick="javascript:handleTableRowClick(event);">
        <td>28 Jun</td>
        <td><a href="#">123456</a></td>
        <td>Hernandez</td>
        <td>London to New York</td>
      </tr>
      <tr onClick="javascript:handleTableRowClick(event);">
        <td>29 Jun</td>
        <td><a href="#">78910</a></td>
        <td>Abdulameer-Jones</td>
        <td>Berlin to Moscow</td>
      </tr>
      <tr onClick="javascript:handleTableRowClick(event);">
        <td>30 Jun</td>
        <td><a href="#">ABCDS</a></td>
        <td>Xavier</td>
        <td>Singapore to Sydney</td>
      </tr>
    </tbody>
  </table>
</div>
<nav aria-label="Past bookings pagination" class="pagination">
  <a aria-disabled="true" aria-label="Previous Page" href="#">&lt;</a>
  <a aria-current="page" aria-label="Page 1" href="#">1</a>
  <a aria-label="Page 2" href="#">2</a>
  <a aria-label="Page 3" href="#">3</a>
  <span>…</span>
  <a aria-label="Next Page" href="#">&gt;</a>
</nav>

Pagination

A table of data will often require pagination immediately after it.

<nav aria-label="Past bookings pagination" class="pagination">
  <a aria-disabled="true" aria-label="Previous Page" href="#">&lt;</a>
  <a aria-current="page" aria-label="Page 1" href="#">1</a>
  <a aria-label="Page 2" href="#">2</a>
  <a aria-label="Page 3" href="#">3</a>
  <span>…</span>
  <a aria-label="Next Page" href="#">&gt;</a>
</nav>

Pagination is a list of numbered links to pages. Visually we just want to see a list of numbers and some icons that represents next/previous pages but aurally we want to hear

  • Previous page, disabled
  • Page 1, active page
  • Page 2
  • Page 3
  • Next page

.visually-hidden

Above we used a CSS class on the Live region, .visually-hidden. This is a class available in a lot of CSS frameworks (sometimes called sr-only or visuallyhidden) and the idea is that is makes its contents visually hidden but still accessible to a screen-reader.

Further reading:

  1. ARIA Authoring Practices Guide (APG) - Sortable Table Example
  2. MDN - aria-sort
  3. MDN - ARIA live regions
  4. MDN - caption element
  5. .visually-hidden CSS

Want to work with us?

Let’s talk about your project