Skip to content

IconButton, Tooltip: reduce per-instance cost of the built-in tooltip #8719

Open

Description

@mattcosta7

Part of #8676 (P1). Measure with #8714 before and after. This was blocked on #8562, which has merged.

Problem

Button/IconButton.tsx:60 wraps every labelled IconButton in a TooltipV2 unless unsafeDisableTooltip is set. Each tooltip instance:

  • Registers a global Escape handler through useOnEscapePress (TooltipV2/Tooltip.tsx:289), even while the tooltip is closed.
  • Runs an accessibility DOM scan on mount in production (:237-288). warning and invariant are no-ops in production, but their arguments are still computed.

hooks/useOnEscapePress.ts:75 and :82 also call Object.keys(registry) on every register and unregister.

Measured: median of 5 runs, N IconButtons:

N mount, tooltip mount, none unmount, tooltip unmount, none
300 15 ms 9 ms 1.3 ms 0.3 ms
1000 48 ms 26 ms 10.1 ms 0.8 ms
3000 200 ms 73 ms 75.0 ms 2.8 ms

Unmount cost grows super-linearly, about 58x for 10x the buttons. Mount cost grows slightly faster than linear. At a few hundred buttons the cost is small.

Proposed fix

  • Subscribe to Escape only while the tooltip is open.
  • Wrap the accessibility scan in __DEV__.
  • Replace Object.keys(registry).length with a counter.

Don't lazy-mount the tooltip element, because aria-labelledby needs it in the DOM. The fix must work on React 18 and 19.

Acceptance criteria

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions