Skip to content

feat(tooltip): modernize Tooltip to Material Design 3#4994

Open
burczu wants to merge 8 commits into
callstack:mainfrom
burczu:feat/tooltip-md3-modernization
Open

feat(tooltip): modernize Tooltip to Material Design 3#4994
burczu wants to merge 8 commits into
callstack:mainfrom
burczu:feat/tooltip-md3-modernization

Conversation

@burczu

@burczu burczu commented Jun 10, 2026

Copy link
Copy Markdown

Motivation

The Tooltip component had drifted from the current Material Design 3 tooltip spec, and only supported the plain (text-only) variant. As part of the v6 MD3 modernization effort (alongside TextInput, Switch, Checkbox, FAB), this brings the tooltip up to spec and adds the missing rich variant:

  • Plain tooltip used the wrong color roles (onSurface/surface) and type style (labelLarge), and had no show/hide transition.
  • Rich tooltips (title + supporting text + actions) weren't possible at all — the title: string prop can't express them.

Related issue

Closes #4980.

What changed

  • Plain tooltip — spec fixes: container onSurfaceinverseSurface, text surfaceinverseOnSurface, type style labelLargebodySmall (12sp). Public API unchanged.
  • Fade animation: plain (and rich) tooltips now fade in/out via Reanimated, driven by the MD3 motion tokens (short3/standardDecelerate in, short2/standardAccelerate out), and honor reduce-motion.
  • New Tooltip.Rich (compound component): an optional title subhead, content (string → bodyMedium, or a custom element for inline links), and actions (button row). Rendered on a surfaceContainer Surface at elevation level 2 with a 12dp corner and 312dp max-width. Uncontrolled tap-to-toggle; dismisses on outside tap, action press, or re-tap; on web it opens on hover and bridges the trigger→tooltip gap.
  • Tokens extracted to Tooltip/tokens.ts; shared fade lifecycle extracted to a useTooltipFade hook used by both variants.
  • Example + docs: new "Rich tooltips" section in the example app; RichTooltip registered in the docs component map; JSDoc usage for both variants.

Test plan

  • yarn test — 743 passing / 170 snapshots (extended Tooltip.test.tsx with MD3 color/typography, fade mount-through-exit, and a Tooltip.Rich suite: toggle, custom content, color roles, backdrop/action dismiss, web hover open + gap-bridge).
  • yarn typescript, yarn lint — clean.
  • Verified on the iOS simulator (iPhone 17 Pro):
    • Plain: long-press an icon button → dark inverseSurface container, smaller bodySmall text, fades in/out.
    • Rich: tap + on the new "Rich tooltips" row → surfaceContainer card with title/body/actions; dismisses on outside-tap, action press, or re-tap; info icon shows a body-only variant.

adrcotfas and others added 8 commits June 9, 2026 14:03
Container now uses inverseSurface and text uses inverseOnSurface per the
MD3 tooltip spec (previously onSurface/surface). Text variant changes from
labelLarge to bodySmall (12sp). Tokens are extracted into a new tokens.ts
following the FAB pattern; the rich and motion token sets land here too and
are consumed in later commits.
The plain tooltip now fades in on show and out on hide using Reanimated,
per the MD3 motion spec (enter short3/standardDecelerate, exit
short2/standardAccelerate). Show/hide intent (visible) is split from mount
state (rendered) so the tooltip stays mounted through the exit fade before
unmounting. Honors reduce-motion via useReduceMotion.
Adds Tooltip.Rich, a persistent, interactive rich tooltip per the MD3 spec:
an optional subhead title, supporting body text (string or element) and a
row of action buttons on a surfaceContainer surface at elevation level 2
with a 12dp corner.

Exposed as a compound component (Object.assign) so the plain Tooltip stays
untouched. Uncontrolled tap-to-toggle: tapping the trigger toggles it,
tapping the Portal backdrop or selecting an action dismisses it. On web it
opens on hover and bridges the trigger-to-tooltip gap before hiding. Reuses
the plain tooltip's Reanimated fade and reduce-motion handling.
Adds a 'Rich tooltips' section to the example app (full title/content/actions
variant plus a body-only one), registers the RichTooltip page in the docs
component map so the generated docs cover Tooltip.Rich, and cross-references
the rich variant from the plain Tooltip JSDoc.
The fade lifecycle (mount-through-exit, opacity, measurement, motion
configs, reduce-motion) was duplicated between Tooltip and Tooltip.Rich.
Extract it into a useTooltipFade hook so both variants share one
implementation. No behavior change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: modernize Tooltip to the latest Material Design specs

2 participants