2

Hi i'm working on a table that i want to improve it's accesibility, but the thing is that it was created with the table rows having the "onClick" attribute and when reading the cells and table with a screen reader such as VoiceOver, NVDA or JAWS it doesn't read the row as clickable or any sign that would tell the user that clicking that row would take him to another page.

The table is created dynamically by JQuery Datatables and a snippet of the table is like this:

<div id="DataTables_Table_1_wrapper" class="dataTables_wrapper" role="grid">
  <table
    class="list lista table table-bordered table-hover dlaList component-editable dataTable"
    data-editable-id="dla28475"
    style="width: 900px"
    id="DataTables_Table_1"
  >
    <thead>
      <tr role="row">
        <th
          class="sorting"
          tabindex="0"
          rowspan="1"
          colspan="1"
          style="width: 230px"
        >
          Activities
        </th>
        <th
          class="sorting_asc"
          tabindex="0"
          rowspan="1"
          colspan="1"
          style="width: 570px"
          aria-sort="ascending"
        >
          Description
        </th>
        <th
          class="sorting"
          tabindex="0"
          rowspan="1"
          colspan="1"
          style="width: 100px"
        >
          Status
        </th>
      </tr>
    </thead>
    <tbody role="alert" aria-live="polite" aria-relevant="all">
      <tr class="odd" onclick="functionForEvent();">
        <td class="">
          <div class="input-sm" status_id="undefined">Blah Blah</div>
        </td>
        <td class="sorting_1">
          <div class="input-sm" status_id="undefined">Blah blah</div>
        </td>
        <td class="">
          <div class="input-sm" status_id="undefined">blah blah</div>
        </td>
      </tr>
    </tbody>
  </table>
</div>

The solution that i created for the moment is to add an<a></a> sorrounding each <td> therefore making the screen reader say "link" but i have seen other tables saying "clickable" without needing a link, but i don't know how they did it by looking at the HTML, they just seem to be regular tables with onClick on tr elements.

A bad sign here could be the "role" attribute breaking the semantics as i have read, the table wasn't created by me so therefore i don't know if that could be the case.

JustAJavaUser
  • 89
  • 2
  • 9

2 Answers2

12

Preword

Apologies to OP there are a few things in this answer that may be misleading if the row is meant to take people to another page.

Firstly for options 1 and 2 below swap <button>s for <a>. There is no need for an event listener or onclick as anchors do everything that is needed.

For option 3 I would add a little extra bit to the text that explains this takes you to a new page.

If you opt for using the column text as the accessible text (i.e. don't override with aria-label) then you should add aria-describedby and add a paragraph that says ", click to open in new page".

<p id="newPageText" class="visually-hidden">click to open (new page)</p>

<tr class="odd" tabindex="0" aria-describedby="newPageText">
        <td class="">
          <div class="input-sm" status_id="undefined">Blah Blah</div>
        </td>
        <td class="sorting_1">
          <div class="input-sm" status_id="undefined">Blah blah</div>
        </td>
        <td class="">
          <div class="input-sm" status_id="undefined">blah blah</div>
        </td>
      </tr>

This would then read "Blah Blah, Blah Blah, Blah Blah, click to open in new page" for screen readers. This then fills in the information that a screen reader would read out with semantics (that we lose) by not being able to use an anchor

Short Answer

The reason your aren't hearing any announcement is that your rows are not focusable.

An item must be focusable in order for there to be a click announcement.

Adding tabindex="0" will fix this issue for keyboard users, for mouse users you should add cursor: pointer and .tr:hover{} rules to your CSS.

However there are better options as detailed below.

Long Answer

Having a whole row clickable is not the best idea.

However it would still be preferable to some other options that I detail in "what NOT to do" section so I have included it as the third option below.

Here are a few options from best to worst in terms of expected behaviour and accessibility vs time to implement (the second option is the optimal one but may take a lot of effort to implement).

1. GOOD - Add a button to a column instead - easy to implement

Add a column to the start (or end if more appropriate) of the row and add <button> into that column to perform the action.

Remove your click handler on the row and attach it to the <button> (also change it to an event listener in your JavaScript rather than an inline onclick to make maintainability easier).

Within this button we add some visually hidden text for screen reader users / text only browser users to make the row association clear.

I recommend Visually hidden text as it is still far more robust than aria-label sadly (although things are getting better!) and has the added benefit of working all the way back to ie6 (although if your site works in IE6 you are better than me! :-) )!

You can build the string within the visually hidden <span> on the server or build it via JavaScript.

It does not need to contain all of the row information if there is a particular column that is unique / descriptive enough to know what you are editing. I have only added all 3 columns' text to the example as I do not know your data.

Example 1

.visually-hidden { 
    border: 0;
    padding: 0;
    margin: 0;
    position: absolute !important;
    height: 1px; 
    width: 1px;
    overflow: hidden;
    clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
    clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
    clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
    white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
<table>
<tbody>
      <tr class="odd">
       <td>
           <button>
               Perform Action
               <span class="visually-hidden">
                   Column 1, Column 2, Column 3
               </span>
           </button>
       </td>
        <td class="">
          <div class="input-sm" status_id="undefined">Column 1</div>
        </td>
        <td class="sorting_1">
          <div class="input-sm" status_id="undefined">Column 2</div>
        </td>
        <td class="">
          <div class="input-sm" status_id="undefined">Column 3</div>
        </td>
      </tr>
    </tbody>
    </table>

This is the most robust way across all devices and easy to implement which is why it is the top recommendation, however it adds a lot of tab stops and is not the best option if you have time to implement the grid pattern

2. BEST - Recommended Way (if you can't add a column) - hard to implement

Use role="grid" and follow the grid pattern.

role="grid" has good screen reader support and is the most semantically appropriate role.

There is a lot more required here as you need to enable keyboard controls such as the use of arrow keys etc.

You would then add a visually hidden paragraph on the page that says "click to edit" and give this an id (e.g. id="editText").

Then on each <td> you would add aria-describedby="editText" so that the <td> would read it's contents followed by "click to edit".

At this point you just add an event listener in your JavaScript to listen for clicks (rather than having inline onclick attributes.) This answer should give you an idea of how to add an event listener to each <td> if you are unsure.

W3C example

no example from me as this way requires more work than a Stack Overflow answer justifies! hehe. However it is a good pattern to reuse across the site if you can make it a reusable component. It has a significant advantage that it only introduces one tab stop.

3. AVERAGE - Possible way if you can't use role="grid" or add a column.

Add tabindex="0" to the <tr>.

This will make the row focusable and then announce "clickable" when the user focuses the row via keyboard.

For mouse users who use a screen reader there will still be no click announcement but that is what cursor: pointer and .tr:hover{} are for (change the cursor and also ensure visually it is clear this is a clickable region when hovering with the mouse with some visible indicator).

All the row columns will get read out one after the other as the text when it is focused, this may not be appropriate so you might want to add an aria-label as an override in browsers that support it.

Make sure you style your tr:focus indicator to be visible and clear if doing this.

tr:hover{
   cursor: pointer;
   background-color: #cccccc;
}
tr:focus{
   border: none;
   outline: 2px solid #333;
   outline-offset: 2px;
}
<table
    class="list lista table table-bordered table-hover dlaList component-editable dataTable"
    data-editable-id="dla28475"
    style="width: 900px"
    id="DataTables_Table_1"
  >

    <tbody>
      <tr class="odd" tabindex="0">
        <td class="">
          <div class="input-sm" status_id="undefined">Blah Blah</div>
        </td>
        <td class="sorting_1">
          <div class="input-sm" status_id="undefined">Blah blah</div>
        </td>
        <td class="">
          <div class="input-sm" status_id="undefined">blah blah</div>
        </td>
      </tr>
    </tbody>
  </table>

The above is the simplest fix but not ideal from an accessiblity perspective due to introducing a lot of tab stops. It will still be usable though on a table with only a few rows.


What NOT to do

DO NOT wrap a row in an anchor <a>

This is not semantically correct and invalid HTML anyway. It would cause issues with some screen readers.

DO NOT use role="alert" on a <tbody>.

If you are attempting to alert screen readers to changes in a table ask another question detailing what changes you are trying to announce and I will help you with that.

At the moment it will start reading the table on page load and with every change. It will also read the whole table again even if you just add a row etc. and will probably read the whole table every time you apply a filter. As you can imagine for a screen reader user this would become a nightmare!

DO NOT add <buttons> to each <td> or make each <td> (cell) clickable with tabindex="0"

This would be a nightmare for keyboard users who rely on the Tab key to navigate (i.e. non screen reader users).

You add so many unnecessary tab stops the page may become unusable.

A few other observations / suggestions

  1. If the table is very long and you end up using option 1 or 3 (adding a tab stop for each row) make sure there is a skip link before the table.
  2. Remove role="alert" and aria-live="polite" as mentioned earlier.
  3. with your sortable column headers you would be better adding a <button> within the <th> that triggers the sort function, it comes with all your focus states built in etc.
  4. Remove role="grid" from the <div> - this should sit on the <table> instead if you use that pattern (option 2 I gave).
  5. Use event handlers instead of onclick attribute - just a general tip for performance and maintainability - nothing to do with accessibility!
GrahamTheDev
  • 22,724
  • 2
  • 32
  • 64
  • This was a really thorough answer, thanks Graham. Yeah i saw that this table has weird attributes in weird ways, even the table isn't meant to be editable but it has attributes that may be seing as the intention of being editable. The table wasn't done by myself so therefore i had to go through with what it was being build first. Thank you. – JustAJavaUser Nov 11 '20 at 16:29
  • Yeah I hate inheriting something like this, I feel your pain! Sometimes it is easier to just rip it apart and start again. If you do then go for the `role="grid"` pattern as that would be best. Also apologies for the mistake with ` – GrahamTheDev Nov 11 '20 at 16:32
0

Attempting to make <tr> elements clickable is likely going to be problematic, and I wouldn't recommend it.

Additionally, I wouldn't recommend wrapping <td> elements with <a> because it will not be valid HTML. As such, assistive technology may behave in un-predicable ways.

Some possible ways you could go about this:

Using Native HTML Semantics with Button Elements

You would probably want to style the buttons so that they have transparent backgrounds, no borders, and occupy the full width of the table cell.

<tbody role="alert" aria-live="polite" aria-relevant="all">
    <tr class="odd">
        <td class="">
            <button onclick="functionForEvent()" aria-label="some helpful info on what clicking this will do">
                <div class="input-sm" status_id="undefined">Blah Blah</div>
            </button>
        </td>
        <td class="sorting_1">
            <button onclick="functionForEvent()" aria-label="some helpful info on what clicking this will do">
                <div class="input-sm" status_id="undefined">Blah blah</div>
            </button>
        </td>
        <td class="">
            <button onclick="functionForEvent()" aria-label="some helpful info on what clicking this will do">
                <div class="input-sm" status_id="undefined">blah blah</div>
            </button>
        </td>
    </tr>
</tbody>

Using ARIA on Table Cells

<tbody role="alert" aria-live="polite" aria-relevant="all">
    <tr class="odd">
        <td class="" tabindex="0" onclick="functionForEvent();" aria-label="some helpful info on what clicking this will do">
            <div class="input-sm" status_id="undefined">Blah Blah</div>
        </td>
        <td class="sorting_1" tabindex="0" onclick="functionForEvent();" aria-label="some helpful info on what clicking this will do">
            <div class="input-sm" status_id="undefined">Blah blah</div>
        </td>
        <td class="" tabindex="0" onclick="functionForEvent();" aria-label="some helpful info on what clicking this will do">
            <div class="input-sm" status_id="undefined">blah blah</div>
        </td>
    </tr>
</tbody>

Using ARIA on Inner Divs

<tbody role="alert" aria-live="polite" aria-relevant="all">
    <tr class="odd">
        <td class="">
            <div tabindex="0" onclick="functionForEvent()" aria-label="some helpful info on what clicking this will do" class="input-sm" status_id="undefined">Blah Blah</div></a>
        </td>
        <td class="sorting_1">
            <div tabindex="0" onclick="functionForEvent()" aria-label="some helpful info on what clicking this will do" class="input-sm" status_id="undefined">Blah blah</div>
        </td>
        <td class="">
            <div tabindex="0" onclick="functionForEvent()" aria-label="some helpful info on what clicking this will do" class="input-sm" status_id="undefined">blah blah</div>
        </td>
    </tr>
</tbody>

Just FYI the role="alert" on the tbody establishes an ARIA live region on the table, which will announce any changes to the content via assistive technology. This shouldn't break any semantics, but it may or may not be what you want.

Josh
  • 3,872
  • 1
  • 12
  • 13
  • That was really helpful, also yeah, i didn't found anything on clickable tables which made me think as you said, that this isn't a standard or even a good practice, the table was made without taking accesibility into account. But i will try some of your suggestions, thanks. – JustAJavaUser Nov 11 '20 at 14:28