feat(widgets): custom tooltip system for widget buttons#10435
feat(widgets): custom tooltip system for widget buttons#10435chrisgervang wants to merge 18 commits into
Conversation
Replace native HTML title tooltips with themed, positioned tooltips using a Preact Tooltip component and @floating-ui/dom. Zero changes to core. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
display: contents elements have no bounding box, so computePosition must reference the actual child button element. Also switch to pointerover/pointerout which bubble through pointer-events: none parents. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ant show - Fix vertical centering by using inline critical styles (whiteSpace/fontSize/padding) so computePosition gets correct dimensions regardless of stylesheet load timing - Fix button group border-radius by using descendant selectors for button - Show tooltips instantly with opacity transition to prevent flash - Use ref callback instead of useEffect for immediate positioning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a styled, theme-aware tooltip system to @deck.gl/widgets using a new Preact <Tooltip> component (positioned via @floating-ui/dom), and wires it into bundled button widgets so consumers can override tooltip content with string | HTMLElement (e.g. for shortcut hints) without touching @deck.gl/core.
Changes:
- Introduces a new Preact
Tooltipcomponent and exports it as_Tooltipfor custom widget authors. - Updates
IconButtonto use the custom tooltip system (replacing nativetitle) and propagates new per-widget tooltip override props across bundled button widgets. - Adds tooltip theming CSS variables and documentation for customization and usage.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| modules/widgets/src/lib/components/tooltip.tsx | New tooltip component using Floating UI for positioning. |
| modules/widgets/src/lib/components/icon-button.tsx | Integrates tooltips into IconButton and replaces native title. |
| modules/widgets/src/zoom-widget.tsx | Adds zoomInTooltip/zoomOutTooltip and passes through to IconButton. |
| modules/widgets/src/fullscreen-widget.tsx | Adds enterTooltip/exitTooltip and passes through to IconButton. |
| modules/widgets/src/toggle-widget.tsx | Adds tooltip/onTooltip overrides and conditionally selects tooltip per state. |
| modules/widgets/src/theme-widget.tsx | Adds lightModeTooltip/darkModeTooltip and passes through to IconButton. |
| modules/widgets/src/screenshot-widget.tsx | Adds tooltip override and passes through to IconButton. |
| modules/widgets/src/reset-view-widget.tsx | Adds tooltip override and passes through to IconButton. |
| modules/widgets/src/icon-widget.tsx | Adds tooltip override and passes through to IconButton. |
| modules/widgets/src/compass-widget.tsx | Switches custom button markup to use <Tooltip> directly. |
| modules/widgets/src/gimbal-widget.tsx | Switches custom button markup to use <Tooltip> directly. |
| modules/widgets/src/stylesheet.css | Adds tooltip trigger + tooltip styling and theme variable hooks. |
| modules/widgets/src/index.ts | Exports _Tooltip and its props for external Preact widget usage. |
| docs/api-reference/widgets/tooltips.md | New docs page describing tooltip overrides, styling, and custom usage patterns. |
| docs/api-reference/widgets/styling.md | Documents --tooltip-* CSS variables. |
| docs/api-reference/widgets/overview.md | Links to the new tooltip documentation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Tooltip props now accept `string | HTMLElement | false`: - `false` explicitly disables the tooltip (no fallback to label) - `undefined` (default) falls back to the widget's label - Update docs to document the `false` option Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch from pointerOver/Out to pointerEnter/Leave to prevent spurious tooltip flicker when moving between child elements. Remove unimplemented aria-describedby and :focus-visible claims from docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests were checking button.title which no longer exists after tooltip system replaced native titles with aria-label + custom tooltips. Also update selectors for compass/gimbal widgets where tooltip wrapper sits between .deck-widget-button and button. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks Chris. I compared this against #10414 / #10427, and I like this direction. In particular, avoiding a new core A few things I think we should tighten before merging:
Overall this is promising. If those edges are addressed, I would prefer converging on this PR over the stacked helper/lifecycle approach. |
- Fix HTMLElement tooltip never refreshing (use replaceChildren) - Fix ThemeWidget tooltip using wrong mode branch - Add tooltip tests: string override, false disables, HTMLElement content, HTMLElement replacement, ThemeWidget mode alignment - Document tooltip override props on all individual widget API pages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ba2eb05. Configure here.
- Fix HTMLElement tooltip never refreshing (use replaceChildren) - Fix ThemeWidget tooltip using wrong mode branch - Add tooltip tests: string override, false disables, HTMLElement content, HTMLElement replacement - Add ThemeWidget tooltip tests (mode alignment, label fallback) - Document tooltip override props on all individual widget API pages - Document automatic IconButton tooltip behavior for Selector, Loading, Timeline, Stats, and Popup widgets Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The per-widget tooltip props are already documented on each widget's own API page — maintaining a duplicate list invites staleness. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…gets Describe that `label` drives a styled tooltip (not a native title), and link to the tooltips page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow users to customize or disable the Play/Pause button tooltips. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These widgets previously only showed tooltips from their label with no way to override or disable them independently. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Widgets like CompassWidget re-render on every viewport change. The inline ref was calling replaceChildren on every render even when the tooltip content hadn't changed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ibgreen-openai
left a comment
There was a problem hiding this comment.
Very nice indeed, big thanks for doing this!!!
Some inline thoughts on the docs, really just some food for thought based on my reaction.
High level comments
- In an ideal world, I would be able to add something to the tooltips (key bindings) rather than replace the tooltips wholesale which means that I also have to duplicate the labels.
But I am not convinced it is worth extra props or callback tooltip props etc. - Also in my apps, I will be changing the tooltips sometimes during widget lifetime especially custom widget buttons as the keyboard binding changes with changing application state, so if the tests cover that it would be nice.
| btn.setAttribute('aria-label', 'My Action'); | ||
| btn.setAttribute('aria-describedby', 'my-tooltip'); | ||
|
|
||
| const tooltip = document.createElement('div'); |
There was a problem hiding this comment.
This is a nit: This example is rather confusing to me and makes the tooltip system read as more complex than it is.
My knee jerk reaction, is why would we suggest users to do all this? It makes it seems very complicated.
For the folks who don't want to import preact and configure JSX in their project for just one custom widget, can't we just export the a plain JS function that renders the internal Preact into an HTML element and this also handles the positioning so we don't have to ask the user to do all this and install the external library etc.
Separately, since we clearly care about Accessibility, for me it would make more sense to have a section that describes Accessibility first (perhaps in every widget page).
Then the example we are giving here would seem less random, as it is clearly implementing the explicit accessibility spec above, rather than forcing the user to try to understand what those HTML element stanzas are for - are they standard DOM fields or some deck convention etc.
There was a problem hiding this comment.
Idea: since we clearly care about Accessibility, for me it would make more sense to have a section that describes Accessibility support/considerations first (perhaps even in every widget page).
There are clearly a number of things that need to be done, setting roles and aria-... attributes etc. and not every developer is up to date on that.
Then the non-Preact example we are giving would seem less random to, as it would clearly be implementing the explicit accessibility spec above, rather than forcing the user to try to understand what those HTML element stanzas are for - are they standard DOM fields or some deck.gl/widgets convention etc.
| btn.addEventListener('pointerenter', () => { tooltip.hidden = false; }); | ||
| btn.addEventListener('pointerleave', () => { tooltip.hidden = true; }); | ||
|
|
||
| rootElement.replaceChildren(btn, tooltip); |
There was a problem hiding this comment.
Replacing all children seems a bit heavy handed?

Background
Built-in widgets use native HTML
titleattributes which are slow (~2s delay), unstyled, and cannot contain rich content like keyboard shortcuts. Prior PRs (#10414, #10427) required adding lifecycle hooks to core — this approach avoids any core changes.Change List
<Tooltip>component using@floating-ui/domfor positioning (flip/shift/offset)IconButtonso all button widgets get styled tooltips automatically*Tooltipprops (string | HTMLElement) for user overrides (e.g. keyboard shortcuts)<Tooltip>directly.deck-widget-tooltipCSS class with theme-aware variables (--tooltip-max-width,--tooltip-z-index)_Tooltipcomponent for Preact-based custom widget authorsdocs/api-reference/widgets/tooltips.mdcovering built-in customization, Preact usage, and vanilla JS/CSS-only guidance@deck.gl/coreNote
Low Risk
UI-only widget layer changes with no @deck.gl/core modifications; behavior is covered by updated widget tests and accessibility shifts from title to aria-label plus role=tooltip.
Overview
Replaces slow native
titletooltips on built-in button widgets with themed, immediate tooltips powered by a new PreactTooltipcomponent (@floating-ui/dompositioning).IconButtonwraps buttons when tooltip content exists;labelstays asaria-labelwhile optionaltooltip/ per-widget*Tooltipprops acceptstring | HTMLElement | falsefor overrides or disabling. Compass, Gimbal, Zoom, Fullscreen, Theme, Timeline, Toggle, and other button widgets are wired through;_Tooltipis exported for custom Preact widgets. Adds.deck-widget-tooltiptheme CSS variables,tooltips.md, and widget API doc updates; tests assertaria-labeland tooltip behavior instead oftitle.Reviewed by Cursor Bugbot for commit 88f2ad9. Bugbot is set up for automated code reviews on this repo. Configure here.