[EuiToolTip] Replace all title attributes with EuiToolTip#9643
[EuiToolTip] Replace all title attributes with EuiToolTip#9643weronikaolejniczak wants to merge 18 commits into
Conversation
0ad67e1 to
5a469df
Compare
556f497 to
40b8e10
Compare
40b8e10 to
22a9f6d
Compare
There was a problem hiding this comment.
Pull request overview
This PR replaces a wide set of native HTML title attributes with EuiToolTip usages to standardize tooltip behavior and reduce duplicate/verbose screen reader announcements across EUI components. It also extends EuiToolTip’s display prop to support flex layouts.
Changes:
- Replaced multiple
title-based tooltips withEuiToolTipacross core components (tables, pagination, breadcrumbs, date pickers, etc.) - Updated truncation behavior to use
EuiToolTip+ keyboard focus support (EuiTextTruncate) - Updated unit/Cypress tests and snapshots to reflect new tooltip DOM and a11y behavior, and added a new story variant for the Markdown editor
Reviewed changes
Copilot reviewed 68 out of 68 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/website/docs/components/display/avatar.mdx | Add accessibility guidance for avatar tooltips |
| packages/eui/src/components/tool_tip/tool_tip.tsx | Allow display="flex" on tooltip anchors |
| packages/eui/src/components/tool_tip/tool_tip.styles.ts | Add flex anchor display styling |
| packages/eui/src/components/timeline/snapshots/timeline.test.tsx.snap | Snapshot updates for avatar tooltip wrapper |
| packages/eui/src/components/timeline/snapshots/timeline_item.test.tsx.snap | Snapshot updates for avatar tooltip wrapper |
| packages/eui/src/components/text_truncate/text_truncate.tsx | Replace truncation title with EuiToolTip + tabindex |
| packages/eui/src/components/text_truncate/text_truncate.test.tsx | Add/adjust tooltip-related unit tests |
| packages/eui/src/components/text_truncate/text_truncate.spec.tsx | Update Cypress spec for tooltip/tabindex behavior |
| packages/eui/src/components/table/table_pagination/snapshots/table_pagination.test.tsx.snap | Snapshot updates for pagination tooltip wrappers |
| packages/eui/src/components/table/table_header_cell.test.tsx | Add assertion for header cell title behavior |
| packages/eui/src/components/selectable/selectable.spec.tsx | Cypress spec updates removing title assertions |
| packages/eui/src/components/selectable/selectable_list/selectable_list.tsx | Only set title when no tooltip/truncation props |
| packages/eui/src/components/search_bar/search_box.tsx | Preserve consumer onFocus while showing hint |
| packages/eui/src/components/search_bar/search_bar.tsx | Wrap search box with EuiToolTip for errors |
| packages/eui/src/components/search_bar/search_bar.test.tsx | Update tests away from findByTitle |
| packages/eui/src/components/search_bar/filters/field_value_selection_filter.spec.tsx | Cypress spec updates removing title selectors |
| packages/eui/src/components/search_bar/snapshots/search_bar.test.tsx.snap | Snapshot updates for tooltip wrapper markup |
| packages/eui/src/components/pagination/pagination_button_arrow.tsx | Replace pagination arrow title with EuiToolTip |
| packages/eui/src/components/pagination/snapshots/pagination.test.tsx.snap | Snapshot updates for pagination tooltip wrappers |
| packages/eui/src/components/markdown_editor/markdown_editor.stories.tsx | Add “NoPlugins” story variant |
| packages/eui/src/components/markdown_editor/markdown_editor_help_button.tsx | Replace/adjust help button tooltip behavior |
| packages/eui/src/components/markdown_editor/snapshots/markdown_editor.test.tsx.snap | Snapshot updates for tooltip wrapper IDs |
| packages/eui/src/components/header/header.a11y.tsx | Update selector away from title |
| packages/eui/src/components/form/field_password/field_password.tsx | Replace toggle button title with EuiToolTip |
| packages/eui/src/components/form/field_password/field_password.test.tsx | Update unit tests removing title expectations |
| packages/eui/src/components/form/field_password/snapshots/field_password.test.tsx.snap | Snapshot updates for tooltip wrappers |
| packages/eui/src/components/date_picker/super_date_picker/time_window_buttons.tsx | Remove explicit title="" suppression on buttons |
| packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx | Replace quick select button title with EuiToolTip |
| packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/snapshots/quick_select_popover.test.tsx.snap | Snapshot updates for tooltip wrapper markup |
| packages/eui/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx | Replace date popover button title with EuiToolTip |
| packages/eui/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx | Replace submit button title with EuiToolTip |
| packages/eui/src/components/date_picker/super_date_picker/date_popover/snapshots/date_popover_button.test.tsx.snap | Snapshot updates removing title |
| packages/eui/src/components/date_picker/super_date_picker/snapshots/time_window_buttons.test.tsx.snap | Snapshot updates showing returned title attrs |
| packages/eui/src/components/date_picker/super_date_picker/snapshots/super_date_picker.test.tsx.snap | Snapshot updates for tooltip wrapper markup |
| packages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsx | Replace auto-refresh title with EuiToolTip |
| packages/eui/src/components/date_picker/auto_refresh/snapshots/auto_refresh.test.tsx.snap | Snapshot updates for tooltip wrapper markup |
| packages/eui/src/components/datagrid/data_grid.test.tsx | Update assertions away from getByTitle |
| packages/eui/src/components/datagrid/controls/keyboard_shortcuts.tsx | Disable SR output for toolbar tooltip |
| packages/eui/src/components/datagrid/controls/display_selector.tsx | Disable SR output for toolbar tooltip |
| packages/eui/src/components/datagrid/controls/data_grid_toolbar.stories.tsx | Disable SR output for story tooltip |
| packages/eui/src/components/datagrid/controls/column_sorting_draggable.test.tsx | Update tests away from .title usage |
| packages/eui/src/components/datagrid/controls/snapshots/keyboard_shortcuts.test.tsx.snap | Snapshot updates for tooltip wrapper IDs |
| packages/eui/src/components/datagrid/controls/snapshots/display_selector.test.tsx.snap | Snapshot updates for tooltip wrapper IDs |
| packages/eui/src/components/datagrid/body/cell/data_grid_cell_actions.tsx | Replace expand button title with EuiToolTip (flex) |
| packages/eui/src/components/datagrid/body/cell/data_grid_cell_actions.test.tsx | Update tests removing title expectations |
| packages/eui/src/components/datagrid/snapshots/data_grid.test.tsx.snap | Snapshot updates for pagination tooltip wrappers |
| packages/eui/src/components/comment_list/snapshots/comment.test.tsx.snap | Snapshot updates for avatar tooltip wrappers |
| packages/eui/src/components/comment_list/snapshots/comment_timeline.test.tsx.snap | Snapshot updates for avatar tooltip wrappers |
| packages/eui/src/components/comment_list/snapshots/comment_list.test.tsx.snap | Snapshot updates for avatar tooltip wrappers |
| packages/eui/src/components/comment_list/snapshots/comment_event.test.tsx.snap | Snapshot updates for avatar tooltip wrappers |
| packages/eui/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx | Only set title for truncation-only cases |
| packages/eui/src/components/card/snapshots/card.test.tsx.snap | Snapshot updates for avatar tooltip wrappers |
| packages/eui/src/components/breadcrumbs/_breadcrumb_content.tsx | Replace breadcrumb popover title with EuiToolTip |
| packages/eui/src/components/breadcrumbs/snapshots/breadcrumbs.test.tsx.snap | Snapshot updates for breadcrumb tooltip wrappers |
| packages/eui/src/components/breadcrumbs/snapshots/breadcrumb.test.tsx.snap | Snapshot updates for breadcrumb tooltip wrappers |
| packages/eui/src/components/breadcrumbs/snapshots/_breadcrumb_content.test.tsx.snap | Snapshot updates for breadcrumb tooltip wrappers |
| packages/eui/src/components/basic_table/default_item_action.tsx | Remove disabled title, ensure aria-disabled handling |
| packages/eui/src/components/basic_table/collapsed_item_actions.tsx | Replace title with tooltip + aria-disabled |
| packages/eui/src/components/basic_table/basic_table.tsx | Replace checkbox titles with tooltips; minor a11y tweaks |
| packages/eui/src/components/basic_table/basic_table.test.tsx | Update disabled assertions to aria-disabled |
| packages/eui/src/components/basic_table/snapshots/pagination_bar.test.tsx.snap | Snapshot updates for pagination tooltip wrappers |
| packages/eui/src/components/basic_table/snapshots/in_memory_table.test.tsx.snap | Snapshot updates for checkbox/pagination tooltips |
| packages/eui/src/components/basic_table/snapshots/collapsed_item_actions.test.tsx.snap | Snapshot updates for tooltip wrapper IDs |
| packages/eui/src/components/basic_table/snapshots/basic_table.test.tsx.snap | Snapshot updates for checkbox/pagination tooltips |
| packages/eui/src/components/avatar/avatar.tsx | Replace avatar title with EuiToolTip |
| packages/eui/src/components/avatar/avatar.test.tsx | Add hover tooltip test |
| packages/eui/src/components/avatar/snapshots/avatar.test.tsx.snap | Snapshot updates for avatar tooltip wrapper markup |
| packages/eui/changelogs/upcoming/9643.md | Changelog entry for tooltip migration + flex display |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
💚 Build SucceededHistory
|
💔 Build Failed
Failed CI StepsHistory
|
| const content = ( | ||
| <div | ||
| tabIndex={isTruncating ? 0 : undefined} | ||
| className={classNames('euiTextTruncate', className)} | ||
| css={styles.euiTextTruncate} | ||
| ref={refs} | ||
| title={isTruncating ? text : undefined} | ||
| {...rest} |
| {(() => { | ||
| const searchBox = ( | ||
| <EuiSearchBox | ||
| {...box} | ||
| query={queryText} | ||
| onSearch={this.onSearch} | ||
| isInvalid={error != null} | ||
| aria-describedby={ | ||
| isHintVisible ? `${this.hintId}` : undefined | ||
| } | ||
| hint={ | ||
| hint | ||
| ? { | ||
| isVisible: isHintVisible, | ||
| setIsVisible: (isVisible: boolean) => { | ||
| this.setState({ isHintVisible: isVisible }); | ||
| }, | ||
| id: this.hintId, | ||
| ...hint, | ||
| } | ||
| : undefined | ||
| } | ||
| /> | ||
| ); | ||
|
|
||
| return error ? ( | ||
| <EuiToolTip content={error.message} display="block"> | ||
| {searchBox} | ||
| </EuiToolTip> | ||
| ) : ( | ||
| searchBox | ||
| ); | ||
| })()} |
mgadewoll
left a comment
There was a problem hiding this comment.
I manually tested the listed components and almost everything works as expected. I found one small A11y regression:
EuiBreadcrumbsdoesn't read the tooltip in NVDA
Screen.Recording.2026-05-21.at.11.05.08.mov
ℹ️ I smoke tested some codesandboxes but they load with @latest EUI not the PR state, hence we can't verify that the PR changes themselves work there as well.
| </span> | ||
| ))} | ||
| </div> | ||
| <EuiToolTip content={name} disableScreenReaderOutput> |
There was a problem hiding this comment.
Non blocking: This will always add a wrapping tooltip element, even if name="" as in EuiComment (code). The tooltip surpresses opening but the element is anyway in the DOM. For some cases this might be on purpose to prevent re-rendering, in this case we could render it conditionally.
| display="block" | ||
| disableScreenReaderOutput={!isInvalid && !needsUpdating} | ||
| anchorProps={{ | ||
| css: css` |
There was a problem hiding this comment.
Nit: Let's move this to a style class for consistency.
| </EuiLink> | ||
| title ? ( | ||
| <EuiToolTip content={title} disableScreenReaderOutput> | ||
| <EuiLink |
There was a problem hiding this comment.
We can move EuiLink to a variable to prevent duplicating code.
| ); | ||
|
|
||
| return error ? ( | ||
| <EuiToolTip content={error.message} display="block"> |
There was a problem hiding this comment.
While this seems logical, it actually has a flaw: When a user presses ENTER and an error is shown, the component is re-rendered and the focus is lost 😬
Screen.Recording.2026-05-21.at.13.28.00.mov
| {(() => { | ||
| const searchBox = ( | ||
| <EuiSearchBox | ||
| {...box} | ||
| query={queryText} | ||
| onSearch={this.onSearch} | ||
| isInvalid={error != null} | ||
| aria-describedby={ | ||
| isHintVisible ? `${this.hintId}` : undefined | ||
| } | ||
| hint={ | ||
| hint | ||
| ? { | ||
| isVisible: isHintVisible, | ||
| setIsVisible: (isVisible: boolean) => { | ||
| this.setState({ isHintVisible: isVisible }); | ||
| }, | ||
| id: this.hintId, | ||
| ...hint, | ||
| } | ||
| : undefined | ||
| } | ||
| /> | ||
| ); | ||
|
|
||
| return error ? ( | ||
| <EuiToolTip content={error.message} display="block"> | ||
| {searchBox} | ||
| </EuiToolTip> | ||
| ) : ( | ||
| searchBox | ||
| ); | ||
| })()} |
| className={className} | ||
| aria-labelledby={ariaLabelId} | ||
| isDisabled={!enabled} | ||
| hasAriaDisabled={!enabled} |
There was a problem hiding this comment.
Non blocking, just fyi: We don't specifically have to conditionally enable hasAriaDisabled. It would result in exactly the same behavior as having it always enabled.
| return ( | ||
| const content = ( | ||
| <div | ||
| tabIndex={isTruncating ? 0 : undefined} |
There was a problem hiding this comment.
💭 This one is tricky and I understand the intention, but imho we probably shouldn't add a tabIndex. This would result in a lot more tab stops on e.g. regular text nodes which is unexpected.
Considering the previous implementation with title also didn't show on keyboard focus, it might be fine to accept that we have a clear mouse-only feature. For screen reader users the text is available and read.
There was a problem hiding this comment.
Oh no, this should not have a tabIndex. I think I added it at one point to come to the same conclusion as you and forgot to remove it.
| const hasOnFocusBadge = | ||
| onFocusBadge && optionIsFocused && !optionIsDisabled; | ||
|
|
||
| const usesTruncation = |
There was a problem hiding this comment.
Nit: The naming is a tiny bit ambiguous since we have native truncation and custom truncation 😅 Maybe hasNativeTruncation would be clearer?
Tip
Review the changes commit-by-commit. These changes need to be on the same PR so that I can easily test them in Kibana and to avoid possible conflicts when merging to main.
Summary
titleHTML attribute cases withEuiToolTip.EuiToolTipdisplaying at once with duplicated content.EuiBetaBadgein favor of: [EuiBetaBadge] Deprecate the component #9343EuiBadgebecause tooltip is displayed both on the text and the icon button. The icon is a child of the content which would mean only the top-level tooltip gets triggered. If we use a browser tooltip for the content andEuiToolTipfor the icon button it creates a weird experience where both can be displayed at once and cover each other.EuiButtonGroupbecause the browser tooltip is used for large labels that can be suppressed by passingtitle=""if needed.EuiListGroupItembecause it already makes the best judgement when to show the title and when to show the tooltip.Note
All remaining
titleattributes are ones that have to do with truncation. The effort and impact of conditionally renderingEuiToolTipis high but the value is low.API Changes
N/A
Screenshots
EuiAvatar(Storybook)Hover an avatar with a
nameprop to see the name as a tooltip.EuiBasicTable(Storybook)Hover the select-all / row-select checkboxes, row action icons (especially disabled ones), and pagination arrows.
Remaining
title: hover truncated column headers.EuiBreadcrumbs(Storybook)Hover the breadcrumb that opens a popover (the one with a chevron icon).
Remaining
title: hover a regular breadcrumb link.EuiComboBox(Storybook)Hover an option that has a tooltip (see "With Tooltip" story).
truncationPropsstory result comes from changes made toEuiTextTruncate.Remaining
title:EuiComboBoxis anEuiBadgefor which we left the browser tooltips.EuiDataGrid(Storybook)Hover the in-cell expand button.
The display and keyboard shortcuts toolbar buttons already had
EuiToolTip.disableScreenReaderOutputwas added to prevent duplicate SR announcements.EuiAutoRefresh(Storybook)Hover the auto-refresh button. Tested with
EuiSuperDatePicker.EuiSuperDatePicker(Storybook)Hover the start / end date display buttons, the invalid field after inputting from date, the submit (✓) button inside the absolute date tab when writing a date in the field, the quick-select (calendar) toggle button, and the auto-refresh button when
isPausedis set tofalse.EuiFieldPassword(Storybook)Hover the show / hide-password (eye) button next to the input.
EuiMarkdownEditor(Storybook)Hover the "Syntax help" button (M↓) in the editor footer.
EuiPagination(Storybook)Hover the previous / next (and first / last, when shown) arrow buttons. Tested with
EuiBasicTable.EuiSearchBar(Storybook)Enter an invalid query to see the error message as a tooltip on the search input.
EuiSelectable(Storybook)Hover an option in the selectable list. No conflicting tooltips.
Remaining
title: when tooltip ortruncationPropsare not provided.EuiTextTruncate(Storybook)Hover the rendered text. Tooltip should appear only when the text is actually truncated.
Impact Assessment
Note: Most PRs should be tested in Kibana to help gauge their Impact before merging.
Impact level: 🟡 Moderate
Kibana PR: WIP
Release Readiness
Figma: {link to Figma or issue}Adoption plan (new features): {link to issue/doc or outline who will integrate this and where}QA instructions for reviewer
Test these components in Storybook:
EuiAvatar(Storybook, Docs)EuiToolTipEuiBasicTable(Storybook, Docs)EuiBreadcrumbs(Storybook, Docs)EuiComboBox(Storybook, Docs)EuiDataGrid(Storybook, Docs)EuiToolTipEuiSuperDatePicker(Storybook, Docs)EuiToolTipEuiFieldPassword(Storybook, Docs)EuiToolTipEuiMarkdownEditor(Storybook, Docs)EuiSearchBar(Storybook, Docs)EuiToolTipEuiSelectable(Storybook, Docs)EuiTextTruncate(Storybook, Docs)EuiToolTipChecklist before marking Ready for Review
Cypress, and VRTBreaking changes: Addedbreaking changelabel (if applicable)Reviewer checklist