Description
Our use of the HTML disabled attribute on buttons removes them from the accessibility tree, making them invisible to screen readers and keyboard users and preventing us from providing critical context to all users. We should use aria-disabled instead.
Problem to solve
Pervasive accessibility issue in EUI (and Kibana by extension)
Our buttons all use the HTML5 disabled attribute to remove buttons' default behavior.
As a user, seeing a button as disabled (rather than not at all) often provides critical context for users. They see it, so they know there is an option to take some action, but that action not possible right now.
For example, imagine a "Submit" button that is disabled. As a user, you'd see that button, now that you can take that "Submit" action, but it's not possible right now because there's something else you need to do first.
To further help provide that context, we often provide tooltips on those buttons that provide additional context. "This button is disabled because you need to do XYZ first.".
This is all useful context, all of which is invisible to screenreaders. They have no knowledge of the existence of that button or the additional context provided.
<EuiToolTip
position="top"
content={<p>This button is disabled because you need to do XYZ first.</p>}
>
<EuiButton disabled={true}>Submit</EuiButton>
</EuiToolTip>
Common point of friction for development teams
Teams often experience churn when they encounter this issue and find there's no current solution. Here's an example:
https://github.com/elastic/kibana-team/issues/1323
Current workarounds
One "workaround" (which is not really a feasible workaround) would be to not use disabled prop and apply all required handling manually as well as re-adding disabled styles.
Another could potentially be to add focusable wrappers with e.g. tooltips instead.
Examples in Kibana
Recent requests or related issues or discussions:
Slack threads
Proposed solution
To improve this behavior we want to replace the usage of the disabled attribute on button DOM elements with aria-disabled. This means:
- the element is still announced in a disabled state to assistive technologies
- the default behavior of
disabled does not apply = the element has still all default events and behaviors
Because the default disabled behavior is removed, we'll need to manually ensure the element behaves as if it was disabled. This means:
- ensuring no unwanted events are triggered (e.g. unsetting of mouse and key events)
- ensuring the disabled styling is applied based on the prop
disabled not the DOM attribute via the selector :disabled
- adjust all test cases that check disabled states
Considerations
-
Based on the amount of different components that would benefit from this update (within EUI as well as on consumer side) we should consider providing a sharable solution (e.g. a hook) to provide the "set-as-disabled" functionality.
-
Updating a basic attribute like disabled might result in a lot of broken tests depending on testing framework and how those handle the check for "isDisabled". It would be wise to run tests/CI early with an early PoC to gage the impact and potential issues early on.
- ⚠ Testing-library's
toBeDisabled does not include aria-disabled (issue)
- ⚠ Selenium uses
isEnabled to check for disabled elements (docs) which only checks for the disabled attribute (code)
- ⚠ Cypress checks disability based only for the
disabled attribute (docs)
- 💭 We might want to consider providing new testing helpers and/or a combined one that checks
disabled and aria-disabled together
Components that use <button> elements with disabled attribute
ℹ This list includes exported components as well as internal ones that are subcomponents of exported components.
- EuiButton
- EuiButtonEmpty
- EuiButtonIcon
- EuiBadge
- EuiCard
- EuiContextMenuItem
- EuiColorPickerSwatch
- EuiSuperDatePicker
- EuiDatePopoverButton
- EuiFilterSelectItem
- EuiFormControlLayoutClearButton
- EuiFormControlLayoutCustomIcon
- EuiRangeThumb
- EuiTickValue
- EuiSuperSelectControl
- EuiSwitch
- EuiImageButton
- EuiLink
- EuiListGroupItem
- EuiPanel
- EuiResizableButton
- EuiSelectableListItem
- EuiSideNavItem
- EuiStepHorizontal
- EuiTab
- EuiTreeViewItem
Some relevant research
WCAG guidance
Historical context
@miukimiu brought up a great question in #5649 with her comment about the isLoading behavior:
To introduce a EuiButtonIcon isLoading state I can follow the other buttons isLoading state approach:
- The button gets
disabled.
- The EuiIcon gets replaced with a EuiLoadingSpinner.
- We introduce a new size to EuiLoadingSpinner
"XXL" so that all the sizes match the EuiIcon sizes.
But I have some a11y questions.
- When the button gets
disabled the focus state is missed.
- It gets removed from the accessibility tree and is invisible to assistive technology users
So when the EuiButtonIcon isLoading would adding an aria-disabled instead of disabled be more appropriate? Or is it OK to keep following the other buttons' pattern?
Description
Our use of the HTML
disabledattribute on buttons removes them from the accessibility tree, making them invisible to screen readers and keyboard users and preventing us from providing critical context to all users. We should usearia-disabledinstead.Problem to solve
Pervasive accessibility issue in EUI (and Kibana by extension)
Our buttons all use the HTML5
disabledattribute to remove buttons' default behavior.As a user, seeing a button as disabled (rather than not at all) often provides critical context for users. They see it, so they know there is an option to take some action, but that action not possible right now.
For example, imagine a "Submit" button that is disabled. As a user, you'd see that button, now that you can take that "Submit" action, but it's not possible right now because there's something else you need to do first.
To further help provide that context, we often provide tooltips on those buttons that provide additional context. "This button is disabled because you need to do XYZ first.".
This is all useful context, all of which is invisible to screenreaders. They have no knowledge of the existence of that button or the additional context provided.
Common point of friction for development teams
Teams often experience churn when they encounter this issue and find there's no current solution. Here's an example:
https://github.com/elastic/kibana-team/issues/1323
Current workarounds
One "workaround" (which is not really a feasible workaround) would be to not use
disabledprop and apply all required handling manually as well as re-adding disabled styles.Another could potentially be to add focusable wrappers with e.g. tooltips instead.
Examples in Kibana
Recent requests or related issues or discussions:
isLoadingto assistive tech #4797Slack threads
Proposed solution
To improve this behavior we want to replace the usage of the
disabledattribute on button DOM elements witharia-disabled. This means:disableddoes not apply = the element has still all default events and behaviorsBecause the default disabled behavior is removed, we'll need to manually ensure the element behaves as if it was disabled. This means:
disablednot the DOM attribute via the selector:disabledConsiderations
Based on the amount of different components that would benefit from this update (within EUI as well as on consumer side) we should consider providing a sharable solution (e.g. a hook) to provide the "set-as-disabled" functionality.
Updating a basic attribute like
disabledmight result in a lot of broken tests depending on testing framework and how those handle the check for "isDisabled". It would be wise to run tests/CI early with an early PoC to gage the impact and potential issues early on.toBeDisableddoes not includearia-disabled(issue)isEnabledto check for disabled elements (docs) which only checks for thedisabledattribute (code)disabledattribute (docs)disabledandaria-disabledtogetherComponents that use
<button>elements withdisabledattributeℹ This list includes exported components as well as internal ones that are subcomponents of exported components.
Some relevant research
WCAG guidance
Historical context
@miukimiu brought up a great question in #5649 with her comment about the
isLoadingbehavior: