Skip to content

[EuiButton] Disabled buttons are invisible to screen readers, creating a UX gap #5666

@1Copenut

Description

@1Copenut

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.

Image
<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

  1. 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.

  2. 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?

Metadata

Metadata

Assignees

No fields configured for Enhancement.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions