IconButton: show label tooltip by default, allow disabling/overriding (mirror Avatar #4164)
- Dominant language
- TypeScript
- Stars
- 13k
- Forks
- 1.1k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 690
Description
## Summary
Show a tooltip **by default** on IconButton, using its `label`, so an icon-only button always reveals what it does on hover. Allow disabling/overriding it the same way as Avatar (#4164): a `tooltip?: ReactNode | boolean` prop, and auto-suppression when the IconButton is already wrapped in a consumer tooltip/hovercard.
## Motivation
IconButton is icon-only by definition — `label` is already required and used as the `aria-label`, so a screen reader always knows the action, but a sighted mouse user gets nothing on hover. An icon-only control with no visible text is the single strongest case for a default tooltip.
Today the tooltip is opt-in: IconButton inherits `Button`'s `tooltip?: string`, and the tooltip only renders when `tooltip != null` (`Button.tsx`, `useTooltip({isEnabled: tooltip != null})`). So every IconButton is silent on hover unless the consumer remembers to pass `tooltip`. We should flip the default: since `label` already exists, default the tooltip to `label`.
## Proposed behavior
- **Default on:** when no `tooltip` is provided, IconButton shows a tooltip with its `label`.
- **Auto-disable when a consumer overlay is present:** if the IconButton is inside a `Tooltip`/`HoverCard` (or a consumer attached tooltip/hovercard trigger behavior), suppress the default tooltip — no double overlay. Same context-based detection as #4164.
- **Escape hatch (mirror #4164):** widen the prop to `tooltip?: ReactNode | boolean`.
- omitted → default tooltip = `label`
- `tooltip={false}` → no tooltip
- `tooltip="…"` / `tooltip={<…>}` → override content (decouples the visible tooltip from the `aria-label`)
- **A11y unchanged:** `label` stays the `aria-label`. The tooltip is an additive visual affordance; don't announce the label twice.
## Design notes
- The tooltip machinery already lives in `Button` (`useTooltip`, `aria-disabled`-when-tooltip-present focus handling). This is mostly: (1) default `tooltip` to `label` in the IconButton wrapper, (2) widen the type from `string` to `ReactNode | boolean`, (3) add the consumer-overlay auto-suppression.
- **Coordinate with `Button.tooltip`.** IconButton is a thin wrapper over `Button` (`Omit`). Decide whether the widened `tooltip?: ReactNode | boolean` and the default-to-label behavior live on `Button` (icon-only branch) or are applied in the IconButton wrapper. Keep the two consistent — a plain `` should behave the same as `` for tooltips, or the difference must be intentional and documented.
- Auto-detection should cover the system's own `Tooltip`/`HoverCard` via context (SSR-safe), with `tooltip={false}` as the explicit opt-out for third-party wrappers — same seam as #4164.
## Acceptance criteria
- [ ] `` shows a "Settings" tooltip on hover/focus with no extra props.
- [ ] `` and `` show **only** the consumer overlay — IconButton's default tooltip is suppressed.
- [ ] `tooltip={false}` disables the tooltip even when standalone.
- [ ] `tooltip="…"` / `tooltip={<…>}` overrides the tooltip content independently of `label`.
- [ ] Accessible name (`label`) is not announced twice.
- [ ] `Button isIconOnly` vs `IconButton` tooltip behavior is consistent (or the difference is intentional + documented).
## Notes
- New/changed public API surface → spec protocol first (Component Lifecycle), and the widened prop + default must land documented (ties into #4163).
- Sibling of #4164 (Avatar) — same `tooltip?: ReactNode | boolean` shape and auto-suppression mechanism; build them with a shared approach.
Contributor guide
Assessment
This issue has not been assessed yet.