Skip to content

Commit 3a2999e

Browse files
authored
Merge branch 'main' into fix/workflows-actions-menu-eui116
2 parents 7e09862 + fb866f1 commit 3a2999e

2,585 files changed

Lines changed: 96123 additions & 33380 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/accessibility/references/components/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ Open the guide that matches the component(s) you are writing or refactoring. Rea
1212
| `interactive_components.md` | Names for interactive controls | `EuiBetaBadge`, `EuiButtonIcon`, `EuiComboBox`, `EuiSelect`, `EuiSuperSelect`, `EuiPagination`, `EuiTreeView`, `EuiBreadcrumbs` |
1313
| `overlays.md` | Modals, flyouts, popovers | `EuiModal`, `EuiFlyout`, `EuiFlyoutResizable`, `EuiConfirmModal`, `EuiPopover` |
1414
| `radio_groups.md` | Radio groups (`name` grouping) | `EuiRadio`, `EuiRadioGroup` |
15-
| `tooltip_icon.md` | Tooltip on icon button (no duplicate SR text) | `EuiToolTip`, `EuiButtonIcon` |
15+
| `tooltip_content.md` | No interactive elements in tooltip `content` / `title` | `EuiToolTip`, `EuiIconTip` |
16+
| `tooltip_icon.md` | Tooltip on icon button (wrap + no duplicate SR text, no native `title`) | `EuiToolTip`, `EuiButtonIcon` |
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# EUI tooltip content: no interactive elements
2+
3+
**Applies to:** `EuiToolTip`, `EuiIconTip` — the `content` and `title` props
4+
5+
Tooltip `content` and `title` render inside a portal with `role="tooltip"`. The overlay only appears while the trigger is hovered or focused and is dismissed on blur, so any focusable element placed inside it is unreachable by keyboard and assistive-technology users. Use **`EuiPopover`** when the content needs to be interactive.
6+
7+
**Related guides:** **`overlays.md`** (`EuiPopover` for interactive content) · **`tooltip_icon.md`** (wrapping `EuiButtonIcon` with `EuiToolTip`) · **`icons_and_tooltips.md`** (`EuiIconTip` vs `EuiToolTip` + `EuiIcon`).
8+
9+
## Canonical usage
10+
11+
- `EuiToolTip` / `EuiIconTip` `content` and `title` may contain:
12+
- **Plain strings** (preferred — easy to localize and read).
13+
- **Non-interactive JSX** — text nodes, `<span>`, `<p>`, `EuiText`, `EuiIcon`, and the display-only badges/cards (`EuiBadge`, `EuiBetaBadge`, `EuiCard`) used **without** `onClick` / `href`.
14+
- They must **not** contain anything focusable:
15+
- Native `<a>`, `<button>`, `<input>`, `<select>`, `<textarea>`.
16+
- Interactive EUI components — `EuiLink`, `EuiButton`, `EuiButtonEmpty`, `EuiButtonIcon`, `EuiFieldText`, `EuiFieldNumber`, `EuiFieldSearch`, `EuiFieldPassword`, `EuiTextArea`, `EuiSelect`, `EuiSuperSelect`, `EuiComboBox`, `EuiSelectable`, `EuiSwitch`, `EuiCheckbox`, `EuiRadio`, `EuiRange`, `EuiDualRange`, `EuiColorPicker`, `EuiDatePicker`, `EuiSuperDatePicker`, `EuiFilterButton`, `EuiPagination`, `EuiTab`, `EuiTreeView`, `EuiContextMenuItem`, `EuiKeyPadMenuItem`, `EuiListGroupItem`, `EuiBreadcrumbs`, `EuiBasicTable`, `EuiInMemoryTable`, `EuiCheckableCard`, …
17+
- The rule searches recursively — interactive elements nested inside fragments, conditional renders (`cond && …`, `cond ? … : …`), or wrapper elements are reported too.
18+
- When users need to interact with the content (click a link, fill a field), switch the wrapper to **`EuiPopover`** triggered by an explicit click — never by hover.
19+
20+
### Manual-review cases (rule is silent)
21+
22+
- **Variable content**`content={tooltipContent}` / `title={titleNode}` is intentionally skipped because it cannot be statically analyzed. Trace the variable and verify it never holds focusable JSX.
23+
- **Conditionally-interactive components**`EuiBadge`, `EuiBetaBadge`, `EuiCard` are excluded from the rule because they render as a plain element without `onClick` / `href`. As soon as you add `onClick` or `href`, they become focusable and the same restriction applies — move the interaction out of the tooltip.
24+
25+
## Examples
26+
27+
```tsx
28+
<EuiToolTip content="Just text">
29+
<EuiButton>Hover me</EuiButton>
30+
</EuiToolTip>
31+
32+
<EuiToolTip content={<EuiText><p>Description</p></EuiText>}>
33+
<EuiButton>Hover me</EuiButton>
34+
</EuiToolTip>
35+
36+
<EuiIconTip content="Informational text" type="info" />
37+
38+
// Display-only badge is fine
39+
<EuiToolTip content={<EuiBadge>v2.0</EuiBadge>}>
40+
<EuiButton>Hover me</EuiButton>
41+
</EuiToolTip>
42+
```
43+
44+
## Common mistakes
45+
46+
```tsx
47+
// WRONG — link inside tooltip is not keyboard-reachable
48+
<EuiToolTip content={<EuiLink href="/docs">Learn more</EuiLink>}>
49+
<EuiButton>Hover me</EuiButton>
50+
</EuiToolTip>
51+
52+
// RIGHT — switch to EuiPopover so the link participates in the focus order
53+
const [isOpen, setIsOpen] = useState(false);
54+
const togglePopover = () => setIsOpen((open) => !open);
55+
const closePopover = () => setIsOpen(false);
56+
57+
<EuiPopover
58+
button={<EuiButton onClick={togglePopover}>More info</EuiButton>}
59+
isOpen={isOpen}
60+
closePopover={closePopover}
61+
>
62+
<EuiLink href="/docs">Learn more</EuiLink>
63+
</EuiPopover>
64+
65+
// WRONG — button inside `EuiIconTip` content
66+
<EuiIconTip content={<EuiButton>Click</EuiButton>} type="info" />
67+
68+
// RIGHT — keep the icon tip purely informational
69+
<EuiIconTip content="Informational text" type="info" />
70+
71+
// WRONG — interactive element inside `title` is also reported
72+
<EuiToolTip title={<EuiLink href="#">Learn more</EuiLink>} content="Info">
73+
<EuiButton>Hover</EuiButton>
74+
</EuiToolTip>
75+
76+
// WRONG — interactive child wrapped in a fragment is reported recursively
77+
<EuiToolTip content={<><span>Text</span><EuiLink href="#">Link</EuiLink></>}>
78+
<EuiButton>Hover</EuiButton>
79+
</EuiToolTip>
80+
81+
// WRONG — interactive child behind `cond && …` is reported
82+
<EuiToolTip content={<span>{cond && <EuiLink href="#">Link</EuiLink>}</span>}>
83+
<EuiButton>Hover</EuiButton>
84+
</EuiToolTip>
85+
86+
// WRONG — interactive child inside a ternary is reported
87+
<EuiToolTip content={cond ? <EuiLink href="#">Link</EuiLink> : null}>
88+
<EuiButton>Hover</EuiButton>
89+
</EuiToolTip>
90+
91+
// WRONG — conditionally-interactive badge with onClick becomes focusable
92+
<EuiToolTip content={<EuiBadge onClick={onClick}>Open</EuiBadge>}>
93+
<EuiButton>Hover</EuiButton>
94+
</EuiToolTip>
95+
```

.agents/skills/accessibility/references/components/tooltip_icon.md

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,58 @@
22

33
**Applies to:** `EuiToolTip`, `EuiButtonIcon`
44

5-
When **`EuiToolTip`** wraps **`EuiButtonIcon`** and the tooltip **`content`** matches the button's **`aria-label`**, assistive technology can announce the same text twice. Use **`disableScreenReaderOutput`** so the tooltip stays available to sighted users while screen readers hear the name once.
5+
Every **`EuiButtonIcon`** needs two things:
66

7-
**Related guides:** **`focus_and_keyboard.md`** (tooltip anchors / `tabIndex`) · **`icons_and_tooltips.md`** (`EuiIconTip` vs `EuiToolTip` + `EuiIcon`).
7+
1. **A visible tooltip for sighted users** — wrap the button with **`EuiToolTip`**. Do **not** use the native **`title`** prop on `EuiButtonIcon`; browser tooltips are unstyled, have no delay control, and are not reliably announced by screen readers across browser / AT combinations.
8+
2. **An accessible name for assistive technology** — keep **`aria-label`** on the button.
89

9-
## Canonical usage
10+
When the tooltip **`content`** and the button's **`aria-label`** match (same string, same variable, or same `i18n` call), also set **`disableScreenReaderOutput`** on `EuiToolTip` so screen readers announce the name once instead of twice.
1011

11-
- **`content`** equals **`aria-label`** (same string or same variable / same `i18n` call) → set **`disableScreenReaderOutput`** on **`EuiToolTip`**.
12-
- **`content`** differs from **`aria-label`** → no extra prop; both will be announced as intended.
13-
- Child is not **`EuiButtonIcon`** → this pattern doesn't apply; check the related guides above.
12+
**Related guides:** **`focus_and_keyboard.md`** (tooltip anchors / `tabIndex`) · **`icons_and_tooltips.md`** (`EuiIconTip` vs `EuiToolTip` + `EuiIcon`) · **`tooltip_content.md`** (no interactive elements inside tooltip `content` / `title`).
1413

15-
Prefer a single **`i18n.translate`** call (same id + `defaultMessage`) for both **`content`** and **`aria-label`** so the strings can't drift apart.
14+
## Canonical usage
1615

17-
For `{...tooltipProps}` spreads, merge **`disableScreenReaderOutput`** at the callsite or in the spread source.
16+
1. Wrap every `EuiButtonIcon` with `EuiToolTip`. Remove any `title` prop from the button.
17+
2. Pass the same localized string to `EuiToolTip` `content` and the button's `aria-label`. Prefer a single **`i18n.translate`** call (same id + `defaultMessage`) referenced from both places so the strings cannot drift apart.
18+
3. When `content` and `aria-label` match → add **`disableScreenReaderOutput`** on `EuiToolTip`.
19+
4. When `content` and `aria-label` intentionally differ (the tooltip elaborates beyond the name) → do **not** add `disableScreenReaderOutput`; both will be announced as intended.
1820

1921
## Examples
2022

2123
```tsx
22-
<EuiToolTip
23-
content={i18n.translate('filter.add', { defaultMessage: 'Add filter' })}
24-
disableScreenReaderOutput
25-
>
24+
const editLabel = i18n.translate('myFeature.editItem', {
25+
defaultMessage: 'Edit item',
26+
});
27+
28+
<EuiToolTip content={editLabel} disableScreenReaderOutput>
2629
<EuiButtonIcon
27-
iconType="plusInCircle"
28-
aria-label={i18n.translate('filter.add', { defaultMessage: 'Add filter' })}
29-
onClick={onAdd}
30+
iconType="pencil"
31+
aria-label={editLabel}
32+
onClick={onEdit}
3033
/>
3134
</EuiToolTip>
3235
```
3336

3437
## Common mistakes
3538

3639
```tsx
37-
// WRONG — screen reader announces "Add filter" twice
38-
<EuiToolTip content={label}>
39-
<EuiButtonIcon iconType="plusInCircle" aria-label={label} onClick={onAdd} />
40+
// WRONG — no visible tooltip for sighted users
41+
<EuiButtonIcon iconType="trash" aria-label="Delete" onClick={onDelete} />
42+
43+
// WRONG — native `title` is not reliably announced by screen readers and has no consistent visual styling
44+
<EuiButtonIcon title="Delete" aria-label="Delete" iconType="trash" onClick={onDelete} />
45+
46+
// WRONG — tooltip and `aria-label` without `disableScreenReaderOutput` will lead to SR announcement duplication
47+
<EuiToolTip content="Delete">
48+
<EuiButtonIcon iconType="trash" aria-label="Delete" onClick={onDelete} />
4049
</EuiToolTip>
4150

42-
// RIGHT
43-
<EuiToolTip content={label} disableScreenReaderOutput>
44-
<EuiButtonIcon iconType="plusInCircle" aria-label={label} onClick={onAdd} />
51+
// RIGHT — wrap, keep `aria-label`, and add `disableScreenReaderOutput` when `content` matches
52+
<EuiToolTip content="Delete" disableScreenReaderOutput>
53+
<EuiButtonIcon iconType="trash" aria-label="Delete" onClick={onDelete} />
4554
</EuiToolTip>
4655

47-
// WRONG — different ids, strings may drift apart
56+
// WRONG — different i18n ids may drift apart
4857
content={i18n.translate('a.tooltip', { defaultMessage: 'Add' })}
4958
aria-label={i18n.translate('a.button', { defaultMessage: 'Add' })}
5059

.agents/skills/accessibility/references/eslint.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ Secondary path: when starting from a rule id, jump to the canonical **component
1515
| `@elastic/eui/require-aria-label-for-modals` | [`components/overlays.md`](components/overlays.md) | `{...props}` hides wiring; no visible title without UX change — escalate. |
1616
| `@elastic/eui/require-table-caption` | [`components/data_tables.md`](components/data_tables.md) | `tableCaption` only via `{...tableProps}` — fix at source; no duplicate conflicting captions. |
1717
| `@elastic/eui/sr-output-disabled-tooltip` | [`components/tooltip_icon.md`](components/tooltip_icon.md) | `EuiToolTip` props from spread; child not `EuiButtonIcon`. |
18+
| `@elastic/eui/tooltip-button-icon-wrap` | [`components/tooltip_icon.md`](components/tooltip_icon.md) | `{...props}` without an explicit `title` is silently skipped — verify the spread does not omit a tooltip. No `aria-label` on the button → autofix can't run; supply both `aria-label` and `EuiToolTip` `content`. |
1819
| `@elastic/eui/tooltip-focusable-anchor` | [`components/focus_and_keyboard.md`](components/focus_and_keyboard.md) | `{...anchorProps}` or unknown custom anchor. |
20+
| `@elastic/eui/tooltip-no-interactive-content` | [`components/tooltip_content.md`](components/tooltip_content.md) | Variable content (`content={var}` / `title={var}`) is silently skipped — trace and verify. `EuiBadge` / `EuiBetaBadge` / `EuiCard` with `onClick` / `href` become focusable and need migration to `EuiPopover`. |

0 commit comments

Comments
 (0)