material-ui icon indicating copy to clipboard operation
material-ui copied to clipboard

ListItem checkbox not spoken by screenreader (WCAG)

Open petercutting opened this issue 3 years ago • 4 comments

Duplicates

  • [X] I have searched the existing issues

Latest version

  • [X] I have tested the latest version

Steps to reproduce 🕹

Steps:

  1. install screenreader extension in chrome browser
  2. surf to https://v4.mui.com/components/lists/
  3. scroll down to List Controls - Checkbox example
  4. start screenreader extension
  5. use tab key to move up and down list. screenreader sais "Line item 1" but nothing about checkbox
  6. use space key to set/unset checkbox. screenreader is quiet

Current behavior 😯

screenreader sais nothing about checkbox in listitem

Expected behavior 🤔

screenreader should inform user of checkbox state

Context 🔦

WCAG compliance

Your environment 🌎

npx @mui/envinfo
  Don't forget to mention which browser you used.
  Output from `npx @mui/envinfo` goes here.

petercutting avatar Sep 22 '22 09:09 petercutting

Can i work on this issue?

manjunathkl avatar Sep 29 '22 09:09 manjunathkl

Can i work on this issue?

Can you propose what you have in mind for the fix?

siriwatknp avatar Sep 30 '22 06:09 siriwatknp

So I tested with a screenreader, and, as mentioned, when using the spacebar to check and uncheck it the screen reader says nothing. However, in the list right beneath it that demonstrates checkboxes as a secondary action, the screenreader does say "Checked" and "Unchecked" in response to using the spacebar. The difference between them is that the input elements is the problematic list have the attribute "tabindex = -1", and those in the working list do not have that attribute. Is there any reason the tabindex attribute is necessary in the first list and not the second? Removing this attribute using the devtools solved the issue, so could it just be removed in the actual code as well? If it can be, I would like to make the change as part of hacktoberfest.

livinglifemeaning avatar Oct 06 '22 20:10 livinglifemeaning

TL;DR: it's actually not as simple as tackling the tabindex :'D

long version:

I dug into it a little deeper, and this is what's going on:

  1. In the first checkbox example ("checkbox as a primary action"):

    • The interactive element is the <div> immediately inside <li>; the inspector reveals that it has tabindex="0" and a click event listener. This <div> is the element that's in focus when pressing the spacebar.

    • The checkedness state is conveyed visually (by changing some stuff in nested <span> and <svg> elements), but not programmatically; in other words, there are no attributes on that <div> element that change when its state changes, and therefore screen readers have nothing to announce.

  2. In the second checkbox example ("checkbox as a secondary action"):

    • The interactive element is the <input type="checkbox"> itself. It does not have a tabindex attribute; it doesn't need one because the element is natively interactive, and therefore natively focusable. It also does not have a click event listener, but it still responds to click and keyboard events according to native HTML checkbox mechanics.

    • The <Checkbox> component has an onChange prop, but the inspector reveals that the <input type="checkbox"> element has no change event listener. This is because of how onChange works in React: it basically lets the browser run its native change mechanics on the HTML checkbox (detect suitable click or keyboard event --> toggle checkedness state), and as that happens, also runs whatever function you passed via the onChange prop.

    • Since the native HTML checkbox mechanics are able to run undisturbed, that means that the checked DOM property of the checkbox node toggles appropriately between true and false. And since the reading pointer of the screen reader is currently on the HTML checkbox (remember, that's the interactive element in this case), the screen reader picks up on this change, and is able to announce the change in checkedness state.

Basically, we get to hear about the change in checkedness as it happens if and only if that information is made available to the screen reader in its current reading location. It's not really about whether the checkbox is focusable; in fact, in the first checkbox example, you can click on the checkbox with the mouse, which brings the reading pointer to the checkbox, which means that change in checkedness happens right where the screen reader now is, which means that we get to hear about it.

  1. Now here's the kicker: by using the inspector in examples 1 and 2 and playing around a bit, I noticed an interesting behavior in both cases.

    • Basically, interacting with a checkbox toggles the value of its checked DOM property between true and false, but not the presence or absence of the checked HTML attribute (present basically means true, and absent basically means false).

    • This happens because the DOM property is being handled by the native checkbox mechanics (which React, by design, doesn't interfere with), while the HTML attribute is being manipulated via the checked prop... but said prop is being controlled by an array of numbers stored as a React state, and handled in such a way that doesn't cause React to re-render the component.

    • Long story short, we end up in a situation where the "DOM checkedness" is being toggled, but the "HTML checkedness" stays at the initial value, which means they contradict each other 50% of the time, which is not supposed to happen, and can potentially lead to much worse accessibility issues in certain environments.

  2. Also... there's a number of more typical problems. To name a couple: in the first checkbox example, we have a <div> element that's interactive, but assistive technologies have no way to know that... it might be tempting to do something like giving it the ARIA role="button", which is basically what was done in the second checkbox example... except that's also problematic, because as per the HTML spec, buttons are not allowed to contain nested interactive content (such as checkboxes).

I'm running out of brain juice now, so I guess my overall conclusion is that there are fundamental architectural problems caused by either disregarding or going directly against the native behaviors and semantics, and I don't have any easy fixes for you at the moment, just some food for thought :D

xurxe avatar Dec 12 '22 14:12 xurxe