[eslint-plugin] Added new @elastic/eui/no-nested-copy-tooltip rule - #9852
Conversation
…lint-plugin/changelogs/9852.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| { | ||
| code: dedent` | ||
| const MyComponent = () => ( | ||
| <EuiCopy textToCopy="some text"> |
There was a problem hiding this comment.
A question for the reviewers: should this be considered a problem? I don't think so—perhaps it can be allowed for the sake of flexibility.
There was a problem hiding this comment.
This pattern works reliably. The issue would be if both beforeMessage and child EuiToolTip existed. Because we no longer bubble events as of v118.0.0, only the innermost tooltip shows (most often the EUI internal one that consumers should be able to override with a prop, as is the case with EuiCopy).
I don't see any issue with this composition. And linting should get rid of any ambiguity anyway because of a developer/an agent see this pattern somewhere, decide to use it but with beforeMessage, the linter will flag it.
There was a problem hiding this comment.
Pull request overview
Adds a new a11y-focused ESLint rule to prevent nested/conflicting tooltips when EuiCopy is used with beforeMessage, and updates the existing tooltip-button-icon-wrap rule/docs/tests so the two rules don’t fight each other.
Changes:
- Added new
@elastic/eui/copy-component-rulesrule (report-only) to flagEuiCopy+beforeMessagewhen the render-prop returns anEuiToolTipas the root element. - Updated
@elastic/eui/tooltip-button-icon-wrapto exemptEuiButtonIconinsideEuiCopyfrom the “must be wrapped inEuiToolTip” report. - Registered the new rule in the plugin’s recommended config, and updated tests/docs/changelog.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/eslint-plugin/src/rules/a11y/tooltip_button_icon_wrap.ts | Adds EuiCopy ancestor exception to avoid requiring a wrapper tooltip that would conflict with the new rule |
| packages/eslint-plugin/src/rules/a11y/tooltip_button_icon_wrap.test.ts | Adds test coverage for the new EuiCopy exception behavior (and title reporting) |
| packages/eslint-plugin/src/rules/a11y/copy_component_rules.ts | Introduces the new rule detecting redundant EuiToolTip wrapping inside EuiCopy when beforeMessage is set |
| packages/eslint-plugin/src/rules/a11y/copy_component_rules.test.ts | Adds valid/invalid rule tests for implicit and explicit render-prop returns |
| packages/eslint-plugin/src/index.ts | Registers the new rule and enables it in recommended at warn level |
| packages/eslint-plugin/README.md | Documents the new rule and the updated tooltip-button-icon-wrap exception |
| packages/eslint-plugin/changelogs/upcoming/9852.md | Adds changelog entry for the new rule and the exception update |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
… to beforeMessage The `tooltip-button-icon-wrap` EuiCopy exception previously skipped the missing-tooltip report for any EuiButtonIcon inside EuiCopy. EuiCopy only renders its internal EuiToolTip when `beforeMessage` is set (empty tooltip content is suppressed), so the exception is now conditional on `beforeMessage`. Updated the README and added regression tests for the no-beforeMessage case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@alexwizp ESLint plugin tests are finally resolved in CI: https://buildkite.com/elastic/eui-pull-request-test/builds/7051#019fae26-2f8a-4b88-9fe5-73f34d49660d 🎉 So no need to run them locally, if CI is 🟢 then we're good. |
|
@kapral18 could you also take a look at it since you raised this? 🙏🏻 |
| }; | ||
| }, | ||
| meta: { | ||
| type: 'problem', |
There was a problem hiding this comment.
No action required: I was thinking if this should be a "suggestion" instead because it's a warning and there's no issue at runtime but it will produce an undesired result to what was coded, so I'm fine categorizing it as a "problem".
There was a problem hiding this comment.
This whole PR looks great to me, thank you for tackling it, @alexwizp 🙏🏻 You're a champ!
I see there are only 8 violations in Kibana. All of them use an arrow function expression. There's one potential case we could handle too that won't be flagged, an early return:
<EuiCopy beforeMessage="Click to copy" textToCopy="some text">
{(copy) => {
if (!condition) return null;
return <EuiToolTip content="Copy me"><EuiButton onClick={copy}>Copy</EuiButton></EuiToolTip>;
}}
</EuiCopy>Address PR elastic#9852 review feedback: - Rename the generic `copy-component-rules` rule to `no-nested-copy-tooltip` (file, export, rule id, index registration, README, changelog, messages). - Sort the rule alphabetically among the `no-*` rules in `index.ts`. - Add invalid test cases for a function-expression render prop and a block body with an early return before the tooltip return. - De-duplicate the redundant "Good" example in the README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@elastic/eui/copy-component-rules rule@elastic/eui/no-nested-copy-tooltip rule
kapral18
left a comment
There was a problem hiding this comment.
Leaving a few edge cases around the new static checks. cc: @weronikaolejniczak for a quick glance as well.
|
|
||
| if ( | ||
| !isWrappedByTooltip(node) && | ||
| !isInsideEuiCopyWithBeforeMessage(node) && |
There was a problem hiding this comment.
Minor/nit: the spread handling is asymmetric between the two components here.
hasSpread skips the report when the EuiButtonIcon itself has spread props, on the grounds that its final prop set is not statically knowable. But beforeMessage can arrive on EuiCopy the same way:
<EuiCopy textToCopy="x" {...props}>
{(copy) => <EuiButtonIcon aria-label="Copy" iconType="copy" onClick={copy} />}
</EuiCopy>isInsideEuiCopyWithBeforeMessage returns false here and the button is reported, even though props may well carry beforeMessage. Worth applying the same "can't statically determine" treatment to a spread on the EuiCopy ancestor, or noting explicitly that unknown spread is intentionally treated as absent.
There was a problem hiding this comment.
Withdrawing this one — you're right it isn't worth the complexity. Silently skipping unknown spread is already the convention across this plugin, and Kibana's a11y docs list it as an expected manual-review caveat for this rule. Resolving.
Address kapral18 review feedback on PR elastic#9852: - no-nested-copy-tooltip: resolve the render-prop child by its function container instead of the first JSX expression container, so a leading JSX comment no longer hides a nested EuiToolTip. - Add a generic, component-agnostic `hasMeaningfulAttr` util that treats a statically empty/falsy attribute value (`""`, `{''}`, `{0}`, `{false}`, `{null}`, `{undefined}`) as absent while leaving dynamic values as present. Both rules now use it for `beforeMessage`, so an empty `beforeMessage` neither exempts a bare EuiButtonIcon nor flags a child EuiToolTip. - tooltip-button-icon-wrap: bail the EuiCopy ancestor walk at a JSXAttribute boundary so a button passed through the `beforeMessage` prop (not the render-prop child) is still reported. - Add tests for all three cases and update README wording. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cover meaningful, dynamic, and statically empty/falsy attribute values, plus name lookup and spread-attribute handling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
💚 Build Succeeded
History
|
💚 Build Succeeded
History
|
|
@kapral18 could you review it again? I'm a bit skeptical about fixing very specific edge cases in the static analyzer. It seems to me that the important thing here is to strike a balance between real-world and hypothetical cases. Thanks |
kapral18
left a comment
There was a problem hiding this comment.
LGTM — all three findings fixed, and hasMeaningfulAttr is a better shape than what I suggested: component-agnostic and separately tested. Verified the repros on 4a4fece, including that meaningful/dynamic beforeMessage still behaves as before. Suites green.
On balance vs. hypotheticals: agreed, and I've withdrawn both remaining edge cases. The motivating Kibana case is pivot_function_form.tsx, which carries an eslint-disable for tooltip-button-icon-wrap today — this PR lets us delete it.
weronikaolejniczak
left a comment
There was a problem hiding this comment.
LGTM! 🟢 Thank you both 🙏🏻
…281965) ## Summary Updates the `.agents/skills/accessibility` skill for [elastic/eui#9852](elastic/eui#9852), which added the `@elastic/eui/no-nested-copy-tooltip` rule and gave `@elastic/eui/tooltip-button-icon-wrap` an `EuiCopy` exception. `EuiCopy` already wraps its render-prop child in an `EuiToolTip`, using `beforeMessage` as that tooltip's content. Wrapping the child in another `EuiToolTip` nests two tooltips on one anchor; conversely, an `EuiButtonIcon` inside an `EuiCopy` with a meaningful `beforeMessage` should *not* be wrapped. Without this update the skill's existing "always wrap `EuiButtonIcon` with `EuiToolTip`" guidance contradicts the new rule. **New:** `references/components/copy_tooltip.md` — canonical pattern (`beforeMessage` + `aria-label`, no wrapper), common mistakes, and manual-review cases: no autofix (tooltip `content` and `beforeMessage` cannot be merged automatically), statically empty/falsy `beforeMessage` (no tooltip is rendered, so a wrapper is still required), render-prop roots the rule cannot resolve, and buttons passed through the `beforeMessage` prop itself. **Updated:** - `references/components/index.md` — new guide row. - `references/eslint.md` — `no-nested-copy-tooltip` row; `tooltip-button-icon-wrap` row extended with the `EuiCopy` exception. - `references/components/tooltip_icon.md` — exception callout (`aria-label` still required, `title` still forbidden). `SKILL.md` is unchanged — it routes by table, not by rule id. Note: Kibana pins `@elastic/eslint-plugin-eui` at `2.14.1` and the rule is not in that release, so this documents the guidance ahead of the plugin bump. Lint will not flag these patterns until then. ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) Not applicable — no user-facing text, no runtime code. Documentation-only change to an agent skill; no tests, no release note, no backport. ### Identify risks None. No product code, configuration, or CI behavior is affected. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
…elastic/eui/tooltip-button-icon-wrap` rules at the error level (#281458) ## Summary This PR enforces `@elastic/eui/icon-accessibility-rules` and `@elastic/eui/tooltip-button-icon-wrap` rules at the error level - **`@elastic/eui/icon-accessibility-rules`** — Ensure the `EuiIcon` includes appropriate accessibility attributes. .`EuiIcon` has an accessible name via `title`, `aria-label`, or `aria-labelledby`; otherwise mark it decorative with `aria-hidden={true}`. Do not combine `tabIndex` with `aria-hidden` - **`@elastic/eui/tooltip-button-icon-wrap`** — Ensure `EuiButtonIcon` is wrapped with `EuiToolTip` for sighted users. Browser-native tooltips (the `title` prop) are unstyled, have no delay control, and are not keyboard-accessible. Every icon button should have a visible tooltip so that sighted users who do not rely on screen readers can understand its purpose. ### Changes - `packages/kbn-eslint-config/.eslintrc.js` - This PR also includes fixes for newly discovered issues, as well as a fix for the case where EuiCopy is used with beforeMessage together with EuiTooltip: elastic/eui#9852 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
…lastic#281965) ## Summary Updates the `.agents/skills/accessibility` skill for [elastic/eui#9852](elastic/eui#9852), which added the `@elastic/eui/no-nested-copy-tooltip` rule and gave `@elastic/eui/tooltip-button-icon-wrap` an `EuiCopy` exception. `EuiCopy` already wraps its render-prop child in an `EuiToolTip`, using `beforeMessage` as that tooltip's content. Wrapping the child in another `EuiToolTip` nests two tooltips on one anchor; conversely, an `EuiButtonIcon` inside an `EuiCopy` with a meaningful `beforeMessage` should *not* be wrapped. Without this update the skill's existing "always wrap `EuiButtonIcon` with `EuiToolTip`" guidance contradicts the new rule. **New:** `references/components/copy_tooltip.md` — canonical pattern (`beforeMessage` + `aria-label`, no wrapper), common mistakes, and manual-review cases: no autofix (tooltip `content` and `beforeMessage` cannot be merged automatically), statically empty/falsy `beforeMessage` (no tooltip is rendered, so a wrapper is still required), render-prop roots the rule cannot resolve, and buttons passed through the `beforeMessage` prop itself. **Updated:** - `references/components/index.md` — new guide row. - `references/eslint.md` — `no-nested-copy-tooltip` row; `tooltip-button-icon-wrap` row extended with the `EuiCopy` exception. - `references/components/tooltip_icon.md` — exception callout (`aria-label` still required, `title` still forbidden). `SKILL.md` is unchanged — it routes by table, not by rule id. Note: Kibana pins `@elastic/eslint-plugin-eui` at `2.14.1` and the rule is not in that release, so this documents the guidance ahead of the plugin bump. Lint will not flag these patterns until then. ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) Not applicable — no user-facing text, no runtime code. Documentation-only change to an agent skill; no tests, no release note, no backport. ### Identify risks None. No product code, configuration, or CI behavior is affected. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
…elastic/eui/tooltip-button-icon-wrap` rules at the error level (elastic#281458) ## Summary This PR enforces `@elastic/eui/icon-accessibility-rules` and `@elastic/eui/tooltip-button-icon-wrap` rules at the error level - **`@elastic/eui/icon-accessibility-rules`** — Ensure the `EuiIcon` includes appropriate accessibility attributes. .`EuiIcon` has an accessible name via `title`, `aria-label`, or `aria-labelledby`; otherwise mark it decorative with `aria-hidden={true}`. Do not combine `tabIndex` with `aria-hidden` - **`@elastic/eui/tooltip-button-icon-wrap`** — Ensure `EuiButtonIcon` is wrapped with `EuiToolTip` for sighted users. Browser-native tooltips (the `title` prop) are unstyled, have no delay control, and are not keyboard-accessible. Every icon button should have a visible tooltip so that sighted users who do not rely on screen readers can understand its purpose. ### Changes - `packages/kbn-eslint-config/.eslintrc.js` - This PR also includes fixes for newly discovered issues, as well as a fix for the case where EuiCopy is used with beforeMessage together with EuiTooltip: elastic/eui#9852 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
## Dependency updates `@elastic/eui` - 118.0.0 ⏩ 119.0.0 `@elastic/eslint-plugin-eui` - 2.15.0 ⏩ 2.16.0 `@elastic/eui-test-helpers` - 1.2.0 ⏩ 1.3.0 ## Changes - Added 3 new `EuiFlyoutMenu` i18n tokens (`euiFlyoutMenu.history.tooltip`, `euiFlyoutMenu.pagination.first`, `euiFlyoutMenu.pagination.last`) to `i18n_eui_mapping.tsx` and all translation files - Replaced removed `plusInCircle` icon alias with `plusCircle` in the ML plugin (data frame analytics, anomaly detection jobs, and trained models menus) ## Package updates ### `@elastic/eslint-plugin-eui` [v2.16.0](https://github.com/elastic/eui/blob/main/packages/eslint-plugin/changelogs/CHANGELOG_2026.md) - Added new `@elastic/eui/no-nested-copy-tooltip` rule to improve `EuiCopy` usage ([#9852](elastic/eui#9852)) - Updated `@elastic/eui/tooltip-button-icon-wrap` with an exception for `EuiCopy` usage covered by `@elastic/eui/no-nested-copy-tooltip` ([#9852](elastic/eui#9852)) - Added `button-group-no-invalid-children` rule ([#9849](elastic/eui#9849)) - Added `no-deprecated-icon-aliases` rule ([#9815](elastic/eui#9815)) ### `@elastic/eui` [v119.0.0](https://github.com/elastic/eui/blob/main/packages/eui/changelogs/CHANGELOG_2026.md) - Bundled icon glyph files into `dist/svgs/` and added `dist/eui-icons.json` manifest (mapping icon filename stems to SVG content strings) ([#9873](elastic/eui#9873)) - Added `hasAriaDisabled` prop to `EuiContextMenuItem` ([#9870](elastic/eui#9870)) - Updated `EuiButtonGroup`: ([#9845](elastic/eui#9845)) - Added new render API via `children` prop that allows passing button components as children - Added `variant` prop (usable only with `children` prop) - Added `gutterSize` prop (usable only with `children` and `variant="default"` prop) - Updated `EuiButton`, `EuiButtonEmpty` and `EuiButtonIcon` to consume a `EuiButtonContext` allowing the group to control common props ([#9845](elastic/eui#9845)) - Updated `EuiFlyoutMenu`: ([#9856](elastic/eui#9856)) - Added `leadingActions` and `trailingActions` props, which accept the new `EuiFlyoutMenuAction` type supporting `toolTipContent` and `toolTipProps` - Changed the history trigger icon to `clockCounter` - Added control-group dividers between built-in controls - Added `onFirst`, `onLast` to `pagination`, which render optional buttons for jumping to the beginning and end of the list. - Deprecated additional `EuiIcon` types that are not represented in Figma ([#9815](elastic/eui#9815)) **Bug fixes** - Fixed the missing type declaration for the `focusEuiToolTipTrigger` test helper ([#9870](elastic/eui#9870)) - Fixed generated icon IDs to remain stable during server-side rendering ([#9866](elastic/eui#9866)) - Fixed a visual bug in `EuiButtonGroup` that rendered duplicate borders on disabled buttons ([#9845](elastic/eui#9845)) - The `EuiFlyoutMenu` history popover now only appears when `historyItems` has more than one entry. When there is a single entry in history, only the Back button is shown. ([#9856](elastic/eui#9856)) - Fixed `EuiFlyoutMenu` pagination controls disappearing when `pagination.total` is `1`; the controls now render (as "1 of 1" with both Prev/Next disabled) for any `total` of at least `1` ([#9856](elastic/eui#9856)) **Deprecations** - Deprecated `customActions` and `EuiFlyoutMenuCustomAction` in `EuiFlyoutMenu`; use `trailingActions` and `EuiFlyoutMenuAction` instead ([#9856](elastic/eui#9856)) **Breaking changes** - Removed most deprecated `EuiIcon` types and their icon assets. 16 widely-used deprecated aliases (e.g. `alert`, `search`, `visPie`) remain temporarily and will be removed in a future release ([#9832](elastic/eui#9832)) ([#9815](elastic/eui#9815)) ### `@elastic/eui-test-helpers` [v1.3.0](https://github.com/elastic/eui/blob/main/packages/test-helpers/changelogs/CHANGELOG_2026.md) - Added `EuiDataGridObject` with `rows`, `cell()`, `cells()`, `doActionOnColumn()` and `openFullScreenMode()`/`closeFullScreenMode()` ([#9874](elastic/eui#9874)) - Added `EuiSuperSelectObject` with `selectOptionByValue()`, `selectOptionByLabel()` and `getSelectedValue()` ([#9874](elastic/eui#9874)) - Added `EuiGlobalToastListObject` with a `toasts` locator and `closeAll()` ([#9874](elastic/eui#9874)) --------- Co-authored-by: Lene Gadewoll <lene.gadewoll@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
## Dependency updates `@elastic/eui` - 118.0.0 ⏩ 119.0.0 `@elastic/eslint-plugin-eui` - 2.15.0 ⏩ 2.16.0 `@elastic/eui-test-helpers` - 1.2.0 ⏩ 1.3.0 ## Changes - Added 3 new `EuiFlyoutMenu` i18n tokens (`euiFlyoutMenu.history.tooltip`, `euiFlyoutMenu.pagination.first`, `euiFlyoutMenu.pagination.last`) to `i18n_eui_mapping.tsx` and all translation files - Replaced removed `plusInCircle` icon alias with `plusCircle` in the ML plugin (data frame analytics, anomaly detection jobs, and trained models menus) ## Package updates ### `@elastic/eslint-plugin-eui` [v2.16.0](https://github.com/elastic/eui/blob/main/packages/eslint-plugin/changelogs/CHANGELOG_2026.md) - Added new `@elastic/eui/no-nested-copy-tooltip` rule to improve `EuiCopy` usage ([elastic#9852](elastic/eui#9852)) - Updated `@elastic/eui/tooltip-button-icon-wrap` with an exception for `EuiCopy` usage covered by `@elastic/eui/no-nested-copy-tooltip` ([elastic#9852](elastic/eui#9852)) - Added `button-group-no-invalid-children` rule ([elastic#9849](elastic/eui#9849)) - Added `no-deprecated-icon-aliases` rule ([elastic#9815](elastic/eui#9815)) ### `@elastic/eui` [v119.0.0](https://github.com/elastic/eui/blob/main/packages/eui/changelogs/CHANGELOG_2026.md) - Bundled icon glyph files into `dist/svgs/` and added `dist/eui-icons.json` manifest (mapping icon filename stems to SVG content strings) ([elastic#9873](elastic/eui#9873)) - Added `hasAriaDisabled` prop to `EuiContextMenuItem` ([elastic#9870](elastic/eui#9870)) - Updated `EuiButtonGroup`: ([elastic#9845](elastic/eui#9845)) - Added new render API via `children` prop that allows passing button components as children - Added `variant` prop (usable only with `children` prop) - Added `gutterSize` prop (usable only with `children` and `variant="default"` prop) - Updated `EuiButton`, `EuiButtonEmpty` and `EuiButtonIcon` to consume a `EuiButtonContext` allowing the group to control common props ([elastic#9845](elastic/eui#9845)) - Updated `EuiFlyoutMenu`: ([elastic#9856](elastic/eui#9856)) - Added `leadingActions` and `trailingActions` props, which accept the new `EuiFlyoutMenuAction` type supporting `toolTipContent` and `toolTipProps` - Changed the history trigger icon to `clockCounter` - Added control-group dividers between built-in controls - Added `onFirst`, `onLast` to `pagination`, which render optional buttons for jumping to the beginning and end of the list. - Deprecated additional `EuiIcon` types that are not represented in Figma ([elastic#9815](elastic/eui#9815)) **Bug fixes** - Fixed the missing type declaration for the `focusEuiToolTipTrigger` test helper ([elastic#9870](elastic/eui#9870)) - Fixed generated icon IDs to remain stable during server-side rendering ([elastic#9866](elastic/eui#9866)) - Fixed a visual bug in `EuiButtonGroup` that rendered duplicate borders on disabled buttons ([elastic#9845](elastic/eui#9845)) - The `EuiFlyoutMenu` history popover now only appears when `historyItems` has more than one entry. When there is a single entry in history, only the Back button is shown. ([elastic#9856](elastic/eui#9856)) - Fixed `EuiFlyoutMenu` pagination controls disappearing when `pagination.total` is `1`; the controls now render (as "1 of 1" with both Prev/Next disabled) for any `total` of at least `1` ([elastic#9856](elastic/eui#9856)) **Deprecations** - Deprecated `customActions` and `EuiFlyoutMenuCustomAction` in `EuiFlyoutMenu`; use `trailingActions` and `EuiFlyoutMenuAction` instead ([elastic#9856](elastic/eui#9856)) **Breaking changes** - Removed most deprecated `EuiIcon` types and their icon assets. 16 widely-used deprecated aliases (e.g. `alert`, `search`, `visPie`) remain temporarily and will be removed in a future release ([elastic#9832](elastic/eui#9832)) ([elastic#9815](elastic/eui#9815)) ### `@elastic/eui-test-helpers` [v1.3.0](https://github.com/elastic/eui/blob/main/packages/test-helpers/changelogs/CHANGELOG_2026.md) - Added `EuiDataGridObject` with `rows`, `cell()`, `cells()`, `doActionOnColumn()` and `openFullScreenMode()`/`closeFullScreenMode()` ([elastic#9874](elastic/eui#9874)) - Added `EuiSuperSelectObject` with `selectOptionByValue()`, `selectOptionByLabel()` and `getSelectedValue()` ([elastic#9874](elastic/eui#9874)) - Added `EuiGlobalToastListObject` with a `toasts` locator and `closeAll()` ([elastic#9874](elastic/eui#9874)) --------- Co-authored-by: Lene Gadewoll <lene.gadewoll@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
## Dependency updates `@elastic/eui` - 118.0.0 ⏩ 119.0.0 `@elastic/eslint-plugin-eui` - 2.15.0 ⏩ 2.16.0 `@elastic/eui-test-helpers` - 1.2.0 ⏩ 1.3.0 ## Changes - Added 3 new `EuiFlyoutMenu` i18n tokens (`euiFlyoutMenu.history.tooltip`, `euiFlyoutMenu.pagination.first`, `euiFlyoutMenu.pagination.last`) to `i18n_eui_mapping.tsx` and all translation files - Replaced removed `plusInCircle` icon alias with `plusCircle` in the ML plugin (data frame analytics, anomaly detection jobs, and trained models menus) ## Package updates ### `@elastic/eslint-plugin-eui` [v2.16.0](https://github.com/elastic/eui/blob/main/packages/eslint-plugin/changelogs/CHANGELOG_2026.md) - Added new `@elastic/eui/no-nested-copy-tooltip` rule to improve `EuiCopy` usage ([elastic#9852](elastic/eui#9852)) - Updated `@elastic/eui/tooltip-button-icon-wrap` with an exception for `EuiCopy` usage covered by `@elastic/eui/no-nested-copy-tooltip` ([elastic#9852](elastic/eui#9852)) - Added `button-group-no-invalid-children` rule ([elastic#9849](elastic/eui#9849)) - Added `no-deprecated-icon-aliases` rule ([elastic#9815](elastic/eui#9815)) ### `@elastic/eui` [v119.0.0](https://github.com/elastic/eui/blob/main/packages/eui/changelogs/CHANGELOG_2026.md) - Bundled icon glyph files into `dist/svgs/` and added `dist/eui-icons.json` manifest (mapping icon filename stems to SVG content strings) ([elastic#9873](elastic/eui#9873)) - Added `hasAriaDisabled` prop to `EuiContextMenuItem` ([elastic#9870](elastic/eui#9870)) - Updated `EuiButtonGroup`: ([elastic#9845](elastic/eui#9845)) - Added new render API via `children` prop that allows passing button components as children - Added `variant` prop (usable only with `children` prop) - Added `gutterSize` prop (usable only with `children` and `variant="default"` prop) - Updated `EuiButton`, `EuiButtonEmpty` and `EuiButtonIcon` to consume a `EuiButtonContext` allowing the group to control common props ([elastic#9845](elastic/eui#9845)) - Updated `EuiFlyoutMenu`: ([elastic#9856](elastic/eui#9856)) - Added `leadingActions` and `trailingActions` props, which accept the new `EuiFlyoutMenuAction` type supporting `toolTipContent` and `toolTipProps` - Changed the history trigger icon to `clockCounter` - Added control-group dividers between built-in controls - Added `onFirst`, `onLast` to `pagination`, which render optional buttons for jumping to the beginning and end of the list. - Deprecated additional `EuiIcon` types that are not represented in Figma ([elastic#9815](elastic/eui#9815)) **Bug fixes** - Fixed the missing type declaration for the `focusEuiToolTipTrigger` test helper ([elastic#9870](elastic/eui#9870)) - Fixed generated icon IDs to remain stable during server-side rendering ([elastic#9866](elastic/eui#9866)) - Fixed a visual bug in `EuiButtonGroup` that rendered duplicate borders on disabled buttons ([elastic#9845](elastic/eui#9845)) - The `EuiFlyoutMenu` history popover now only appears when `historyItems` has more than one entry. When there is a single entry in history, only the Back button is shown. ([elastic#9856](elastic/eui#9856)) - Fixed `EuiFlyoutMenu` pagination controls disappearing when `pagination.total` is `1`; the controls now render (as "1 of 1" with both Prev/Next disabled) for any `total` of at least `1` ([elastic#9856](elastic/eui#9856)) **Deprecations** - Deprecated `customActions` and `EuiFlyoutMenuCustomAction` in `EuiFlyoutMenu`; use `trailingActions` and `EuiFlyoutMenuAction` instead ([elastic#9856](elastic/eui#9856)) **Breaking changes** - Removed most deprecated `EuiIcon` types and their icon assets. 16 widely-used deprecated aliases (e.g. `alert`, `search`, `visPie`) remain temporarily and will be removed in a future release ([elastic#9832](elastic/eui#9832)) ([elastic#9815](elastic/eui#9815)) ### `@elastic/eui-test-helpers` [v1.3.0](https://github.com/elastic/eui/blob/main/packages/test-helpers/changelogs/CHANGELOG_2026.md) - Added `EuiDataGridObject` with `rows`, `cell()`, `cells()`, `doActionOnColumn()` and `openFullScreenMode()`/`closeFullScreenMode()` ([elastic#9874](elastic/eui#9874)) - Added `EuiSuperSelectObject` with `selectOptionByValue()`, `selectOptionByLabel()` and `getSelectedValue()` ([elastic#9874](elastic/eui#9874)) - Added `EuiGlobalToastListObject` with a `toasts` locator and `closeAll()` ([elastic#9874](elastic/eui#9874)) --------- Co-authored-by: Lene Gadewoll <lene.gadewoll@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
## Dependency updates `@elastic/eui` - 118.0.0 ⏩ 119.0.0 `@elastic/eslint-plugin-eui` - 2.15.0 ⏩ 2.16.0 `@elastic/eui-test-helpers` - 1.2.0 ⏩ 1.3.0 ## Changes - Added 3 new `EuiFlyoutMenu` i18n tokens (`euiFlyoutMenu.history.tooltip`, `euiFlyoutMenu.pagination.first`, `euiFlyoutMenu.pagination.last`) to `i18n_eui_mapping.tsx` and all translation files - Replaced removed `plusInCircle` icon alias with `plusCircle` in the ML plugin (data frame analytics, anomaly detection jobs, and trained models menus) ## Package updates ### `@elastic/eslint-plugin-eui` [v2.16.0](https://github.com/elastic/eui/blob/main/packages/eslint-plugin/changelogs/CHANGELOG_2026.md) - Added new `@elastic/eui/no-nested-copy-tooltip` rule to improve `EuiCopy` usage ([elastic#9852](elastic/eui#9852)) - Updated `@elastic/eui/tooltip-button-icon-wrap` with an exception for `EuiCopy` usage covered by `@elastic/eui/no-nested-copy-tooltip` ([elastic#9852](elastic/eui#9852)) - Added `button-group-no-invalid-children` rule ([elastic#9849](elastic/eui#9849)) - Added `no-deprecated-icon-aliases` rule ([elastic#9815](elastic/eui#9815)) ### `@elastic/eui` [v119.0.0](https://github.com/elastic/eui/blob/main/packages/eui/changelogs/CHANGELOG_2026.md) - Bundled icon glyph files into `dist/svgs/` and added `dist/eui-icons.json` manifest (mapping icon filename stems to SVG content strings) ([elastic#9873](elastic/eui#9873)) - Added `hasAriaDisabled` prop to `EuiContextMenuItem` ([elastic#9870](elastic/eui#9870)) - Updated `EuiButtonGroup`: ([elastic#9845](elastic/eui#9845)) - Added new render API via `children` prop that allows passing button components as children - Added `variant` prop (usable only with `children` prop) - Added `gutterSize` prop (usable only with `children` and `variant="default"` prop) - Updated `EuiButton`, `EuiButtonEmpty` and `EuiButtonIcon` to consume a `EuiButtonContext` allowing the group to control common props ([elastic#9845](elastic/eui#9845)) - Updated `EuiFlyoutMenu`: ([elastic#9856](elastic/eui#9856)) - Added `leadingActions` and `trailingActions` props, which accept the new `EuiFlyoutMenuAction` type supporting `toolTipContent` and `toolTipProps` - Changed the history trigger icon to `clockCounter` - Added control-group dividers between built-in controls - Added `onFirst`, `onLast` to `pagination`, which render optional buttons for jumping to the beginning and end of the list. - Deprecated additional `EuiIcon` types that are not represented in Figma ([elastic#9815](elastic/eui#9815)) **Bug fixes** - Fixed the missing type declaration for the `focusEuiToolTipTrigger` test helper ([elastic#9870](elastic/eui#9870)) - Fixed generated icon IDs to remain stable during server-side rendering ([elastic#9866](elastic/eui#9866)) - Fixed a visual bug in `EuiButtonGroup` that rendered duplicate borders on disabled buttons ([elastic#9845](elastic/eui#9845)) - The `EuiFlyoutMenu` history popover now only appears when `historyItems` has more than one entry. When there is a single entry in history, only the Back button is shown. ([elastic#9856](elastic/eui#9856)) - Fixed `EuiFlyoutMenu` pagination controls disappearing when `pagination.total` is `1`; the controls now render (as "1 of 1" with both Prev/Next disabled) for any `total` of at least `1` ([elastic#9856](elastic/eui#9856)) **Deprecations** - Deprecated `customActions` and `EuiFlyoutMenuCustomAction` in `EuiFlyoutMenu`; use `trailingActions` and `EuiFlyoutMenuAction` instead ([elastic#9856](elastic/eui#9856)) **Breaking changes** - Removed most deprecated `EuiIcon` types and their icon assets. 16 widely-used deprecated aliases (e.g. `alert`, `search`, `visPie`) remain temporarily and will be removed in a future release ([elastic#9832](elastic/eui#9832)) ([elastic#9815](elastic/eui#9815)) ### `@elastic/eui-test-helpers` [v1.3.0](https://github.com/elastic/eui/blob/main/packages/test-helpers/changelogs/CHANGELOG_2026.md) - Added `EuiDataGridObject` with `rows`, `cell()`, `cells()`, `doActionOnColumn()` and `openFullScreenMode()`/`closeFullScreenMode()` ([elastic#9874](elastic/eui#9874)) - Added `EuiSuperSelectObject` with `selectOptionByValue()`, `selectOptionByLabel()` and `getSelectedValue()` ([elastic#9874](elastic/eui#9874)) - Added `EuiGlobalToastListObject` with a `toasts` locator and `closeAll()` ([elastic#9874](elastic/eui#9874)) --------- Co-authored-by: Lene Gadewoll <lene.gadewoll@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Summary
@elastic/eui/no-nested-copy-tooltipand updates the existing@elastic/eui/tooltip-button-icon-wraprule to avoid a conflict between the two.EuiCopyalready wraps its render-prop child in anEuiToolTipinternally, using thebeforeMessageprop as that tooltip's content. When the render-prop child is itself anEuiToolTip, the result is a nested, conflicting tooltip. The new rule flags this pattern; thetooltip-button-icon-wrapupdate prevents it from demanding a wrapper thatno-nested-copy-tooltipforbids.no-nested-copy-tooltip(a11y): reports whenEuiCopysetsbeforeMessageand its render-prop root child is anEuiToolTip, recommending removal of theEuiToolTipwrapper and moving its message intobeforeMessage. It resolves the render-prop root through implicit arrow returns, block-bodyreturnstatements (including block bodies with an early return), and function expressions. It is report-only (no autofix), because the tooltip'scontentand an existingbeforeMessagevalue cannot always be merged safely (e.g. when either is a variable or JSX).tooltip-button-icon-wrap: added anEuiCopyancestor exception so anEuiButtonIconrendered inside anEuiCopythat setsbeforeMessageis no longer flagged for a missingEuiToolTipwrapper (the tooltip is provided byEuiCopy). WithoutbeforeMessagethere is no tooltip, so the button is still flagged. A browser-nativetitleprop is still reported in all cases.src/index.ts(recommendedconfig,warnlevel), added tests, and updated the pluginREADME.md.API Changes
@elastic/eslint-plugin-euino-nested-copy-tooltipEuiCopy+beforeMessagewith anEuiToolTiprender-prop child@elastic/eslint-plugin-euitooltip-button-icon-wrapEuiToolTipwrapper for anEuiButtonIconrendered inside anEuiCopythat setsbeforeMessageNo changes to the
@elastic/euicomponent public API.Screenshots
N/A — lint-only change, no visual output.Impact Assessment
Note: Most PRs should be tested in Kibana to help gauge their Impact before merging.
recommendedconfig atwarnlevel, so consumers (e.g. Kibana) may see new lint warnings whereEuiCopy+beforeMessagewraps anEuiToolTip. Warnings only — no build breakage.Impact level: 🟢 Low
Release Readiness
packages/eslint-plugin/README.md(rule docs with examples for both rules)FigmaMigration guide— new warn-level rule, no migration requiredAdoption plan— enabled via the existingrecommendedconfigQA instructions for reviewer
yarn testinpackages/eslint-plugin.no_nested_copy_tooltip.test.tspasses (valid/invalid cases for implicit return, expressionbeforeMessage, block-body return, function expression, and early return).tooltip_button_icon_wrap.test.tspasses, including the newEuiCopyexception cases (with and withoutbeforeMessage) and that atitleprop insideEuiCopyis still reported.Checklist before marking Ready for Review
QA: light/dark modes, high contrast, mobile, browsers, keyboard, screen reader— lint-only changeQA: Tested in CodeSandbox and KibanaQA: Tested docs changesBreaking changes: label— not applicableReviewer checklist