How to build accessible & responsive HTML table.

While working on a client project, i was asked to find a way to make HTML table accessible to screen readers. Our design comps required a vertically stacked layout for mobile devices and a horizontally stacked layout on tablet/desktop browsers.

I expected screen readers to be able to read my HTML code and read each cell when entering on it. I though everything would work out of the box, as html table are nothing new and that their implementation is a solved case since a long time ago. I was horribly wrong.

The problem with responsive table.

To achieve responsive layout with my table, i choose to use flexbox. On mobile devices, table rows and cells will be stacked vertically. The problem is that tables are not treated like tables if you change their css display mode. By setting their css display to “block” or “flex”, tables are treated like <div>. The same logics goes for table rows and cells.

To work around this limitation, you have to specify ARIA roles attribute on each table’s rows, cells and on the table himself. Once you know that information, everything make sense and you could figure how to make it work.

The full code

Note: Open this CodePen in a new window to play with window’s size.

Writing notes

  1. <table> must have role=”table” attribute.
  2. <caption> is required even if not visible on screen. 
    He’s used in VoiceOver Rotor menu.
  3. Table Head cells (<th> or <td>) must have id, role=”columnheader” and scope=”col” attributes.
  4. <tr> must have role=”row”.
  5. Table rows are in flexbox + flex-direction: column in mobile and switch to flex-direction: row on tablet.
  6. Table cells must have role=”cell” and headers attributes.
  7. Table cells headers must link to a Table Head cell’s id.
  8. You can put multiple headers inside headers attribute. 
    Put them in a space delimited string like this: headers=”header1 header2"
    Screen Readers will read it one after the other.
  9. You can create a row header by switching a table cell <td> to <th>
    Then, you need to switch role=”cell” to role=”rowheader” and add scope=”row” + id
    Finally, you need to update all cells in this row to add this cell as a headers for other cells (see #8)
    This mean the cell <th> will be a header (role=”columnheader”) for this row (scope=”row”).

I hope this article will be helpful to other people.
Thanks for reading!