Search code examples
htmlhtml-tableaccessibilitywai-ariavoiceover

How do I describe what row/column a <td> cell should read out as with VoiceOver?


I'm trying to make an Excel-style table with the HTML <table> element and and make it accessible to screen readers. I have a decorative row to add column letters ("A", "B", "C", etc) and another to add row numbers ("1", "2", "3", etc), and I'd like the screen reader to not count these as 'rows'/'columns' when telling the user which row/cell they're on. I'm able to hide these decorative elements from the screen reader with aria-hidden="true", but they're still getting included in column/row counts.

Here's an example table that demonstrates the issue:

<table aria-colcount="2" aria-rowcount="2">
    <thead>
        <tr>
            <th aria-hidden="true">#</th>
            <th aria-hidden="true" scope="col" aria-colindex="1">A</th>
            <th aria-hidden="true" scope="col" aria-colindex="2">B</th>
        </tr>
        <tr>
            <th aria-hidden="true"></th>
            <th scope="col" aria-colindex="1">First</th>
            <th scope="col" aria-colindex="2">Second</th>
        </tr>
    </thead>
    <tbody>
        <tr aria-rowindex="1">
            <th scope="row" aria-hidden="true">1</th>
            <td aria-colindex="1">Cell A1</td>
            <td aria-colindex="2">Cell B1</td>
        </tr>
        <tr aria-rowindex="2">
            <th scope="row" aria-hidden="true">2</th>
            <td aria-colindex="1">Cell A2</td>
            <td aria-colindex="2">Cell B2</td>
        </tr>
    </tbody>
</table>

In the example above, if I highlight cell A1 and move my VoiceOver cursor to cell A2, VoiceOver reads out "Row 4 of 4, Cell A2" instead of what I'd like it to read, "Row 2 of 2, Cell A2". Similarly, if I move the VoiceOver cursor from Cell A2 to Cell B2, VoiceOver reads "Second, Cell B2, Column 3 of 3" instead of what I'd like it to read, "Second, Cell B2, Column 2 of 2". How do I force VoiceOver to read out from the aria-*index attributes instead of indexing based on the table?


Solution

  • Screen reader users know that the column and row headers (<th scope="col"> or <th scope="row">) are included in the overall cell count for a table. If you try to force them not to be included, it could cause more confusion that you think you're trying to fix.

    The only way to not include them is to not have them in the same table. You could create two tables and have the column headers in one table and then the data values in a separate table and then associate the column headers to the cells using headers and id.

    But this will cause 2 tables to be read. If you want to prevent the first/headers table from being read as a table, you could try removing the semantics of the headers table by specifying <table role="presentation">, but on some screen reader + browser combinations, it prevents the headers table from being used.

    Also, the headers attribute doesn't seem to be honored on Chrome but is on Firefox, so this solution, although valid HTML, does not work everywhere.

    <table>
      <tr>
        <th scope="col" id="col1">A</th>
        <th scope="col" id="col2">B</th>
      </tr>
    </table>
    
    <table>
      <tr>
        <td headers='col1'>cell a1</td>
        <td headers='col2'>cell b1</td>
      </tr>
      <tr>
        <td headers='col1'>cell a2</td>
        <td headers='col2'>cell b2</td>
      </tr>
    </table>
    

    It's a lot of work to try to "fix" a problem that isn't a problem to screen reader users.