Skip to content

Conversation

@mgadewoll
Copy link
Contributor

@mgadewoll mgadewoll commented Nov 10, 2025

Summary

closes #9098

This PR introduces custom disabled behavior for all base button components (EuiButton, EuiButtonEmpty, EuiButtonIcon, EuiButtonGroup, EuiFilterButton) via a new prop hasAriaDisabled.

Important

The new custom disabled behavior via hasAriaDisabled will be added as opt-in Beta feature and we will monitor usages and adjust/fix the functionality as needed.

If hasAriaDisabled=true is added, the buttons render with the aria-disabled attribute instead of the native disabled attribute. The main purpose for this is to preserve focusability of buttons while also keeping them semantically and functionally disabled. This is useful when wanting to add tooltips on disabled buttons provide additional information about the state.

aria-disabled provides the same semantic state as disabled but besides that does not prevent any events from firing.
This PR introduces a hook for handling disabled behavior that's used under the hood in the updated button components.

The hook ensures that:

  • either aria-disabled or disabled are returned
  • react event handlers are unset when disabled (e.g. onClick)
  • programmatic events are prevented (e.g. .click())
  • native DOM events are prevented when disabled (e.g. via dispatchEvent)
  • focus, blur and required keyboard events are preserved

API changes

These apply to EuiButton, EuiButtonEmpty, EuiButtonIcon, EuiButtonGroup and EuiFilterButton.

new prop type default value description
hasAriaDisabled boolean false Changes the native disabled attribute to aria-disabled to preserve focusability. This results in a semantically disabled button with a custom disabled state behavior instead of the default browser one.

Additional changes

Because usages with hasAriaDisabled render aria-disabled instead of disabled, there are additional considerations for its usage:

  • CSS selectors relying on :disabled won't work
  • disabled test framework matchers won't work

To support the usage of hasAriaDisabled, this PR adds the following:

  • euiDisabledSelector - variable that provides the combined CSS disabled selectors :disabled, [aria-disabled="true"]
  • custom test matchers for the test frameworks used in EUI/Kibana
    • adds .toBeEuiDisabled for react-testing-library
    • adds .toHaveEuiDisabledProp for enzyme
    • adds should('be.euiDisabled) for Cypress

Why are we making this change?

💪 :accessibility: Usability: The update provides an enhanced disabled behavior that preserves focusability and supports consumer requests to allow adding tooltips on disabled buttons.

Screenshots #

Screenshot 2025-11-10 at 18 16 14
Screen.Recording.2025-11-10.at.18.15.56.mov

Impact to users

🟢 There are no immediate updates needed on consumer side. The added functionality is in beta state and opt-in only.

Note

The changes have been run as pre-release in Kibana CI here)

ℹ️ Consumers that want to start using hasAriaDisabled on buttons, will need to update the following if used:

  • test assertions checking for disabled attribute -> use the new EUI test helpers instead
  • style selectors using :disabled pseudo-selector -> use euiDisabledSelector in CSS-in-JS/JS or :disabled, [aria-disabled="true"] in CSS instead

Note

Examples of test updates for RTL, Enzyme, Cypress, FTR and Playwright on Kibana have been prepared here.

QA

Note

It's suggested to review changes commit by commit.

🧪 Testing Story
📖 Documentation:

View added documentation screenshots Screenshot 2025-11-10 at 20 23 49 Screenshot 2025-11-10 at 20 24 49 Screenshot 2025-11-10 at 20 24 30
  • test the updated button components and:
    • verify regular disabled state with hasAriaDisabled=false has no regressions with production
    • verify hasAriaDisabled={true} supports focus, blur and Tab and Escape key events
    • verify focus is preserved on a button that changes isDisabled state
    • verify hasAriaDisabled={true} prevents any mouse/pointer/touch event as well as any other keyboard event (not Tab or Escape key)
  • review the added documentation and confirm it's clear and complete
  • run yarn build and verify that the added test helpers are correctly bundled in @elastic/eui/lib/test

General checklist

  • Browser QA
    • Checked in both light and dark modes
    • Checked in both MacOS and Windows high contrast modes
    • Checked in mobile
    • Checked in Chrome, Safari, Edge, and Firefox
    • Checked for accessibility including keyboard-only and screenreader modes
  • Docs site QA
  • Code quality checklist
  • Release checklist
    • A changelog entry exists and is marked appropriately
    • If applicable, added the breaking change issue label (and filled out the breaking change checklist)
    • If the changes unblock an issue in a different repo, smoke tested carefully (see Testing EUI features in Kibana ahead of time)
  • Designer checklist
    • If applicable, file an issue to update EUI's Figma library with any corresponding UI changes. (This is an internal repo, if you are external to Elastic, ask a maintainer to submit this request)

ToDo

@mgadewoll mgadewoll self-assigned this Nov 10, 2025
@mgadewoll mgadewoll force-pushed the buttons/focusable-disabled-state branch 5 times, most recently from 347efc9 to c0d44e2 Compare November 11, 2025 09:58
@mgadewoll mgadewoll force-pushed the buttons/focusable-disabled-state branch from c0d44e2 to 9e283de Compare November 11, 2025 21:05
@mgadewoll mgadewoll force-pushed the buttons/focusable-disabled-state branch from 9e283de to d9f31e8 Compare November 12, 2025 08:40
@mgadewoll mgadewoll marked this pull request as ready for review November 12, 2025 09:32
@mgadewoll mgadewoll requested a review from a team as a code owner November 12, 2025 09:32
@acstll acstll self-requested a review November 12, 2025 13:48
Copy link
Contributor

@acstll acstll left a comment

Choose a reason for hiding this comment

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

Not approving yet but shared mostly nits and non-blocking suggestions (would like to know what you think). I really like how this turned out and how the featured has been implemented.

I went through the suggested QA steps and it's all working as expected. I also thought (again) about naming of test helpers and they seem just right.

Amazing work ✨

@mgadewoll mgadewoll force-pushed the buttons/focusable-disabled-state branch from d9f31e8 to 2fddd0c Compare November 14, 2025 10:11
@mgadewoll mgadewoll requested a review from acstll November 14, 2025 10:18
Copy link
Contributor

@acstll acstll left a comment

Choose a reason for hiding this comment

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

🟢 Thanks for addressing my comments. The only thing left is reverting the TESTING_EXAMPLE story. Again, awesome work!

@mgadewoll
Copy link
Contributor Author

@acstll Thanks for the thorough review and suggestions! ❤️

@mgadewoll mgadewoll force-pushed the buttons/focusable-disabled-state branch from 2fddd0c to cd47a4f Compare November 14, 2025 12:11
@elasticmachine
Copy link
Collaborator

💚 Build Succeeded

History

cc @mgadewoll

@elasticmachine
Copy link
Collaborator

💚 Build Succeeded

History

cc @mgadewoll

@mgadewoll mgadewoll merged commit b813185 into elastic:main Nov 14, 2025
7 checks passed
acstll added a commit to elastic/kibana that referenced this pull request Nov 20, 2025
- `@elastic/eui`: `v109.0.0` ⏩ `v109.1.0`
- `@elastic/eslint-plugin-eui`: `v2.5.0` ⏩ `v2.6.0`

---

## Changes

- Updated i18n EUI mapping 6cc95b0
- Updated test in Unified Search
668948f

## Package updates

### `@elastic/eui`
[`v109.1.0`](https://github.com/elastic/eui/releases/v109.1.0)

- Added `--euiBottomBarOffset` CSS variable to `EuiBottomBar` for
positioning other fixed elements relative to the bottom bar's height
([#9211](elastic/eui#9211))
- Updated `boxesVertical` icon and restored `checkInCircleFilled`,
`errorFilled`, and `warningFilled` icons.
([#9194](elastic/eui#9194))
- Updated `EuiSuperDatePicker` with new time zone information, opt-in
via `timeZoneDisplayProps`.
([#9191](elastic/eui#9191))
- Updated the position of `EuiModal` by removing bottom padding in
`EuiOverlayMask` ([#9190](elastic/eui#9190))
- Added `EuiPopover` and `EuiToolTip`'s `repositionOnScroll` to
`componentDefaults` ([#9152](elastic/eui#9152))
- Updated `EuiSuperDatePicker` with new time window buttons for time
shifting and zoom out, opt-in via `showTimeWindowButtons` boolean prop.
([#9151](elastic/eui#9151))
- Added beta prop `hasAriaDisabled` to all base button components:
`EuiButton`, `EuiButtonEmpty`, `EuiButtonIcon`, `EuibuttonGroup`,
`EuiFilterButton` ([#9201](elastic/eui#9201))
- Added `euiDisabledSelector` variable that combines CSS selectors
`:disabled` and `[aria-disabled="true"]`
([#9201](elastic/eui#9201))
- Added custom test matchers that check for both `disabled` and
`aria-disabled` attributes:
([#9201](elastic/eui#9201))
  - React testing Library: `.toBeEuiDisabled()`
  - Enzyme: `.toHaveEuiDisabledProp()`
  - Cypress: `should('be.euiDisabled)`

**Bug fixes**

- Fixed unexpected duplicate columns in `EuiDataGrid` crashing the
column sorting by removing duplicate columns entirely
([#9209](elastic/eui#9209))
- Fixed a visual bug in `EuiTable` where long table row content would be
cut off on mobile screens
([#9206](elastic/eui#9206))
- Fixed virtualized `EuiCodeBlock` rendering blank lines when content
updates if scrolled. ([#9196](elastic/eui#9196))
- Fixed `EuiButtonGroup` button sizing to ensure square buttons when
used with `isIconOnly=true`
([#9170](elastic/eui#9170))

**Accessibility**

- Fixed an issue where portalled components like `EuiPopover` were not
included in `EuiFlyout`'s focus trap through
`includeSelectorInFocusTrap`, making them inaccessible to keyboard users
([#9103](elastic/eui#9103))

### `@elastic/eslint-plugin-eui`
[`v2.6.0`](https://github.com/elastic/eui/blob/main/packages/eslint-plugin/changelogs/CHANGELOG_2025.md#v260)

- Added new `require-table-caption` rule.
([#9168](elastic/eui#9168))

---------

Co-authored-by: Elastic Machine <[email protected]>
andrimal pushed a commit to andrimal/kibana that referenced this pull request Nov 20, 2025
- `@elastic/eui`: `v109.0.0` ⏩ `v109.1.0`
- `@elastic/eslint-plugin-eui`: `v2.5.0` ⏩ `v2.6.0`

---

## Changes

- Updated i18n EUI mapping 6cc95b0
- Updated test in Unified Search
668948f

## Package updates

### `@elastic/eui`
[`v109.1.0`](https://github.com/elastic/eui/releases/v109.1.0)

- Added `--euiBottomBarOffset` CSS variable to `EuiBottomBar` for
positioning other fixed elements relative to the bottom bar's height
([elastic#9211](elastic/eui#9211))
- Updated `boxesVertical` icon and restored `checkInCircleFilled`,
`errorFilled`, and `warningFilled` icons.
([elastic#9194](elastic/eui#9194))
- Updated `EuiSuperDatePicker` with new time zone information, opt-in
via `timeZoneDisplayProps`.
([elastic#9191](elastic/eui#9191))
- Updated the position of `EuiModal` by removing bottom padding in
`EuiOverlayMask` ([elastic#9190](elastic/eui#9190))
- Added `EuiPopover` and `EuiToolTip`'s `repositionOnScroll` to
`componentDefaults` ([elastic#9152](elastic/eui#9152))
- Updated `EuiSuperDatePicker` with new time window buttons for time
shifting and zoom out, opt-in via `showTimeWindowButtons` boolean prop.
([elastic#9151](elastic/eui#9151))
- Added beta prop `hasAriaDisabled` to all base button components:
`EuiButton`, `EuiButtonEmpty`, `EuiButtonIcon`, `EuibuttonGroup`,
`EuiFilterButton` ([elastic#9201](elastic/eui#9201))
- Added `euiDisabledSelector` variable that combines CSS selectors
`:disabled` and `[aria-disabled="true"]`
([elastic#9201](elastic/eui#9201))
- Added custom test matchers that check for both `disabled` and
`aria-disabled` attributes:
([elastic#9201](elastic/eui#9201))
  - React testing Library: `.toBeEuiDisabled()`
  - Enzyme: `.toHaveEuiDisabledProp()`
  - Cypress: `should('be.euiDisabled)`

**Bug fixes**

- Fixed unexpected duplicate columns in `EuiDataGrid` crashing the
column sorting by removing duplicate columns entirely
([elastic#9209](elastic/eui#9209))
- Fixed a visual bug in `EuiTable` where long table row content would be
cut off on mobile screens
([elastic#9206](elastic/eui#9206))
- Fixed virtualized `EuiCodeBlock` rendering blank lines when content
updates if scrolled. ([elastic#9196](elastic/eui#9196))
- Fixed `EuiButtonGroup` button sizing to ensure square buttons when
used with `isIconOnly=true`
([elastic#9170](elastic/eui#9170))

**Accessibility**

- Fixed an issue where portalled components like `EuiPopover` were not
included in `EuiFlyout`'s focus trap through
`includeSelectorInFocusTrap`, making them inaccessible to keyboard users
([elastic#9103](elastic/eui#9103))

### `@elastic/eslint-plugin-eui`
[`v2.6.0`](https://github.com/elastic/eui/blob/main/packages/eslint-plugin/changelogs/CHANGELOG_2025.md#v260)

- Added new `require-table-caption` rule.
([elastic#9168](elastic/eui#9168))

---------

Co-authored-by: Elastic Machine <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[EuiButton] Implement focusable disabled state behavior

3 participants