Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Button] Make disabled buttons more accessible #6461

Merged
merged 20 commits into from
Jul 21, 2022

Conversation

zakwarsame
Copy link
Contributor

@zakwarsame zakwarsame commented Jun 30, 2022

WHY are these changes introduced?

  • Buttons are currently insufficiently accessible when the disabled prop is set to true
  • The native disabled HTML attribute causes this by ignoring the entire element when navigating with the Tab or Tab + Shift keys and also makes it difficult for screen readers to locate it.
  • Using the aria-disabled instead makes it easy for screen readers and keyboard users to interpret the button.

Fixes #5967

WHAT is this pull request doing?

  • Replaces disabled with aria-disabled

How to 🎩

  • Using storybook, go to Button > Disabled state, and navigate using the Tab key

🖥 Check out this short demo video

🎩 checklist

@zakwarsame zakwarsame changed the title [Button] Making disabled duttons more accessible [Button] Make disabled buttons more accessible Jun 30, 2022
@github-actions
Copy link
Contributor

github-actions bot commented Jun 30, 2022

size-limit report 📦

Path Size
polaris-react-cjs 198.26 KB (+0.05% 🔺)
polaris-react-esm 133.14 KB (+0.05% 🔺)
polaris-react-esnext 188.29 KB (+0.04% 🔺)
polaris-react-css 41.75 KB (+0.02% 🔺)

Copy link
Contributor

@mrcthms mrcthms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, your logic is pretty sound here. Just a couple of semantic things to take a look at.

Also, you mentioned the disabled buttons still having a focus state when a user presses a key. Ideally we would want to avoid having the button change visually at all when it is disabled, event when focused. It's an industry best practice to do so, and also could cause potential confusion for users if it is the only button on screen and they feel they are interacting with it, but don't have an active button to compare it to note this is a disabled button, rather than us just having super low contrast regular buttons, if that makes sense.

.changeset/hot-crabs-act.md Outdated Show resolved Hide resolved
polaris-react/src/components/Button/Button.tsx Outdated Show resolved Hide resolved
polaris-react/src/components/Button/Button.tsx Outdated Show resolved Hide resolved
@zakwarsame
Copy link
Contributor Author

thanks for the premium feedback marc, should all be resolved :)

Copy link
Contributor

@mrcthms mrcthms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me! We'll probably need to get somebody from @Shopify/polaris-team to take a 👀 too though before we can merge it.

@@ -224,7 +224,7 @@
}

&:hover,
&:focus {
&:focus:not(.disabled) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

Comment on lines 203 to 220
const handleClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
if (disabled) {
event.preventDefault();
return;
}
toggleDisclosureActive();
},
[disabled, toggleDisclosureActive],
);
const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLButtonElement>) => {
if (['Enter', ' '].includes(event.key) && disabled) {
event.preventDefault();
}
},
[disabled],
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know off the top of my head, but I assumed using aria-disabled would have given us this functionality for free. This logic could probably be repeated for all Polaris elements that are disabled.

@sarahill
Copy link
Contributor

sarahill commented Jul 7, 2022

I don't know off the top of my head, but I assumed using aria-disabled would have given us this functionality for free. This logic could probably be repeated for all Polaris elements that are disabled.

+1 aria-disabled should give us what we want here and communicates with folks using screen readers https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled

@zakwarsame
Copy link
Contributor Author

zakwarsame commented Jul 7, 2022

I don't know off the top of my head, but I assumed using aria-disabled would have given us this functionality for free. This logic could probably be repeated for all Polaris elements that are disabled.

+1 aria-disabled should give us what we want here and communicates with folks using screen readers https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled

Yeah we could benefit from the fact that it communicates with assistive technologies. Apart from the semantics, though, we don't get features like click prevention, focus prevention:

While adding disabled to an HTML form control causes :disabled user-agent styles to be applied, adding aria-disabled="true" doesn't, by default, alter an element's appearance. The element can be styled with the attribute selector `[aria-disabled="true"] ...
You should include JavaScript to disable the functionality of the element while also changing the appearance of the element so sighted users know it is disabled. - MDN

A nice table from this article reference:

I'm currently exploring exporting the logic to a custom hook in order to be repeated in other Polaris elements.

@alex-page
Copy link
Member

Hey @zakwarsame I don't think we should ship changes based off the css tricks article. Have we found issues using assistive technologies? Can we replicate the issue you are fixing?

@zakwarsame
Copy link
Contributor Author

zakwarsame commented Jul 11, 2022

Hey @zakwarsame I don't think we should ship changes based off the css tricks article. Have we found issues using assistive technologies? Can we replicate the issue you are fixing?

The source I quoted there is actually from MDN- I only used the table from css tricks to represent what's written in MDN visually. I can understand that we need to confirm it, so I've replicated this myself. Here is how:

Issue

  • What we're trying to fix can be easily replicated on our current storybook by clicking through the disabled buttons with both mouse and keyboard, and observing that nothing happens. Below I'm demonstrating the problem. Neither the keyboard, nor the assistive voice-over picks up that there is a disabled button:

    Current disabled button walk-through video (unmute to hear 🔊) :
    disabled-issue.mov

Solution

  • aria-disabled tackles this by enabling keyboard navigation and indicates to assistive technologies that the element upon which it is set and all focusable descendants are disabled.

    Walk-through showing that with aria-disabled, we can both navigate and get feedback from an assistive technology for indication (unmute to hear 🔊)
    assistive-voiceover.mov

Why we need to additionally disable clicks with javascript on aria-disabled elements

  • Even though we have aria-disabled and pointer-events: none; in our styling for these elements, keyboards are still able to click the buttons.

    Below, notice how nothing happens when I click with a mouse (due to our css code pointer-events: none;) but also nothing prevents the keyboard from clicking:

    Here I'm showing what happens when we click the button with `aria-disabled` and our current styling
    not-quite-fixed.mov

    I've temporarily included the code console.log('This should not be clickable') in the handleClick function for emphasis on problem. It's not in the PR.

Final proposal

  • With this PR, we get the best of aria-disabled and Javascript by stopping the button from being clicked with both mouse and keyboard while maintaining the accessibility functionality.

    Assistive technology picks it up (unmute to hear 🔊):
    assistive-fixed.mov
    Keyboard cannot click button but can navigate
    keyboard-fixed.mov

@alex-page
Copy link
Member

alex-page commented Jul 11, 2022

@zakwarsame this is an excellent write up. Thank you. Honestly this is super.

It's interesting to me that disabled elements can receive focus. What do you think about changing the tabindex? This could stop disabled elements from receiving focus and then if they aren't focused they cannot be clicked on. Is there a reason they need to be focusable?

@zakwarsame
Copy link
Contributor Author

zakwarsame commented Jul 11, 2022

Thank you! I think having them be focusable but styling to indicate that they're not clickable is the perfect compromise.

Keyboard accessibility is one of the most important aspects of web accessibility. Many users with motor disabilities rely on a keyboard. Some people have tremors which don't allow for fine muscle control. Others have little or no use of their hands, or no hands at all. In addition to traditional keyboards, some users may use modified keyboards or other hardware that mimics the functionality of a keyboard. Blind users also typically use a keyboard for navigation. Users without disabilities may use a keyboard for navigation because of preference or efficiency
-WebAIM

Considering that most people with visual impairments will prefer to navigate using the keyboard, wouldn't tabIndex do the opposite of what we're trying to achieve here?

A negative value (usually tabindex="-1") means that the element is not reachable via sequential keyboard navigation...
The user won't be able to focus any element with a negative tabindex using the keyboard - MDN

I'm going to explore this further before merging, for best results.

@zakwarsame
Copy link
Contributor Author

tabIndex is picked up by screen readers. This is the best solution for avoiding keyboard focus and allowing assistive technologies to navigate.

No focus when only navigating with keyboards:
not-focusable.mov
With a screen reader (🔊):
tabIndex-soln.mov

@alex-page alex-page force-pushed the main branch 7 times, most recently from a3a40bb to 0281d22 Compare July 14, 2022 05:31
Copy link
Contributor

@mrcthms mrcthms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of little nits but nothing from my side that would block a release, nice one!

polaris-react/src/utilities/use-disable-interaction.ts Outdated Show resolved Hide resolved
polaris-react/src/utilities/use-disable-interaction.ts Outdated Show resolved Hide resolved
@arthurgouveia
Copy link
Member

@zakwarsame I'm currently having to run Polaris Uplift on Email, and we need to overwrite some properties to make sure it matches our UI.

Normally, I target :disabled for that and got surprised we don't add it do the button. While I can see aria-disabled addition being a good thing, why would we remove the native disabled as well? Would putting it back cause any harm to the current implementation?

From an a11y POV, I don't see how disabled and aria-disabled wouldn't announce the same on a screenreader given both should serve the same purpose, while the first actually also natively makes the button not work.

Surely I can target the aria-disabled but I'm curious as to why we would remove the native browser solution as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make our disabled buttons more accessible
5 participants