Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4a13cc3
feat(widgets): add custom tooltip system for widget buttons
chrisgervang Jul 9, 2026
83e103a
fix(widgets): use firstElementChild for tooltip positioning
chrisgervang Jul 9, 2026
1b184e8
fix(widgets): fix tooltip positioning, button group styling, and inst…
chrisgervang Jul 9, 2026
334933b
feat(widgets): support `false` to explicitly disable tooltips
chrisgervang Jul 9, 2026
f6d5f88
fix(widgets): use pointerEnter/Leave and correct accessibility docs
chrisgervang Jul 10, 2026
63a8a4e
fix(widgets): update tests for tooltip system (title → aria-label)
chrisgervang Jul 10, 2026
b1828c0
docs(widgets): add tooltips page to table of contents
chrisgervang Jul 10, 2026
752d0d5
fix(widgets): address PR review feedback
chrisgervang Jul 10, 2026
ba2eb05
Merge branch 'master' into chr/widget-tooltip-system
chrisgervang Jul 10, 2026
1318337
fix(widgets): address PR review feedback
chrisgervang Jul 10, 2026
f879189
docs(widgets): remove exhaustive tooltip table from overview
chrisgervang Jul 10, 2026
c09b97c
docs(widgets): clarify label tooltip behavior on loading/selector wid…
chrisgervang Jul 10, 2026
ed96783
feat(widgets): add playTooltip/pauseTooltip props to TimelineWidget
chrisgervang Jul 10, 2026
8c3a379
feat(widgets): add tooltip prop to LoadingWidget and SelectorWidget
chrisgervang Jul 10, 2026
3f0e274
fix(widgets): standardize tooltip JSDoc comments
chrisgervang Jul 10, 2026
6011051
docs(widgets): make tooltip prop docs more brief and consistent
chrisgervang Jul 10, 2026
4c165b4
docs(widgets): clarify label vs tooltip design in overview
chrisgervang Jul 10, 2026
88f2ad9
fix(widgets): memoize HTMLElement tooltip ref to avoid DOM thrashing
chrisgervang Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/api-reference/widgets/compass-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ The `CompassWidget` accepts the generic [`WidgetProps`](../core/widget.md#widget

Tooltip message displayed while hovering a mouse over the widget.

#### `tooltip` (string | HTMLElement | false, optional) {#tooltip}

* Default: value of `label`

Custom tooltip content. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `transitionDuration` (number, optional) {#transitionduration}

* Default: `200`
Expand Down
12 changes: 12 additions & 0 deletions docs/api-reference/widgets/fullscreen-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,24 @@ A [compatible DOM element](https://developer.mozilla.org/en-US/docs/Web/API/Elem

Tooltip message displayed while hovering a mouse over the widget when out of fullscreen.

#### `enterTooltip` (string | HTMLElement | false, optional) {#entertooltip}

* Default: value of `enterLabel`

Custom tooltip content for the enter fullscreen button. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `exitLabel` (string, optional) {#exitlabel}

* Default: `'Exit Fullscreen'`

Tooltip message displayed while hovering a mouse over the widget when fullscreen.

#### `exitTooltip` (string | HTMLElement | false, optional) {#exittooltip}

* Default: value of `exitLabel`

Custom tooltip content for the exit fullscreen button. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `onFullscreenChange` (Function, optional) {#onfullscreenchange}

```ts
Expand Down
6 changes: 6 additions & 0 deletions docs/api-reference/widgets/gimbal-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ The `GimbalWidgetProps` accepts the generic [`WidgetProps`](../core/widget.md#wi

Tooltip message displayed while hovering a mouse over the widget.

#### `tooltip` (string | HTMLElement | false, optional) {#tooltip}

* Default: value of `label`

Custom tooltip content. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `strokeWidth` (number, optional) {#strokewidth}

* Default: `1.5`
Expand Down
6 changes: 6 additions & 0 deletions docs/api-reference/widgets/icon-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ Data URL used as the button icon mask.

Tooltip message displayed while hovering over the widget.

#### `tooltip` (string | HTMLElement | false, optional) {#tooltip}

* Default: value of `label`

Custom tooltip content. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `color` (string, optional) {#color}

CSS color applied to the icon.
Expand Down
8 changes: 7 additions & 1 deletion docs/api-reference/widgets/loading-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,13 @@ The `InfoWidget` accepts the generic [`WidgetProps`](../core/widget.md#widgetpro

* Default: `'Loading data'`

Tooltip message displayed while hovering a mouse over the widget.
Text used as the button's `aria-label` and displayed as a styled [tooltip](./tooltips.md) on hover/focus.

#### `tooltip` (string | HTMLElement | false, optional) {#tooltip}

* Default: value of `label`

Custom tooltip content. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `onLoadingChange` (Function, optional) {#onloadingchange}

Expand Down
4 changes: 4 additions & 0 deletions docs/api-reference/widgets/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ new Deck({
});
```

## Tooltips

Built-in button widgets show styled tooltips on hover. See [Widget Tooltips](./tooltips) for customization and usage in custom widgets.

## Themes and Styling

deck.gl widget appearance can be customized using [themes and CSS](./styling).
6 changes: 6 additions & 0 deletions docs/api-reference/widgets/reset-view-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ The `ResetViewWidget` accepts the generic [`WidgetProps`](../core/widget.md#widg

Tooltip message displayed while hovering a mouse over the widget.

#### `tooltip` (string | HTMLElement | false, optional) {#tooltip}

* Default: value of `label`

Custom tooltip content. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `initialViewState` (ViewState, optional) {#initialviewstate}

* Default: `deck.props.initialViewState`
Expand Down
6 changes: 6 additions & 0 deletions docs/api-reference/widgets/screenshot-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ The `ScreenshotWidget` accepts the generic [`WidgetProps`](../core/widget.md#wid

Tooltip message displayed while hovering a mouse over the widget.

#### `tooltip` (string | HTMLElement | false, optional) {#tooltip}

* Default: value of `label`

Custom tooltip content. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `filename` (string, optional) {#filename}

* Default: `'screenshot.png'`
Expand Down
8 changes: 7 additions & 1 deletion docs/api-reference/widgets/selector-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,13 @@ Data URL used as the option icon mask.

#### `label` (string, optional) {#label}

Text shown in the menu and used as the button tooltip when selected.
Text shown in the menu and used as the button's `aria-label` and styled [tooltip](./tooltips.md) when selected.

#### `tooltip` (string | HTMLElement | false, optional) {#tooltip}

* Default: value of `label`

Custom tooltip content. Overrides the default label text in the tooltip. Pass `false` to disable.

## Styles

Expand Down
7 changes: 7 additions & 0 deletions docs/api-reference/widgets/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ Additionally, refer to each widget's API reference for variables specific to tha
| `--menu-text` | [Color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) | `rgb(24, 24, 26)` |
| `--menu-item-hover` | [Color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) | `rgba(0, 0, 0, 0.08)` |

### Tooltip

| Name | Type | Default |
| ---- | ---- | ------- |
| `--tooltip-max-width` | [Dimension](https://developer.mozilla.org/en-US/docs/Web/CSS/dimension) | `240px` |
| `--tooltip-z-index` | Number | `1000` |

### Range input

| Name | Type | Default |
Expand Down
12 changes: 12 additions & 0 deletions docs/api-reference/widgets/theme-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,24 @@ Callback when the user clicks the theme toggle button.

Tooltip message displayed while hovering a mouse over the widget when light mode is available.

#### `lightModeTooltip` (string | HTMLElement | false, optional) {#lightmodetooltip}

* Default: value of `lightModeLabel`

Custom tooltip content when in light mode. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `darkModeLabel` (string, optional) {#darkmodelabel}

* Default: `'Dark Mode'`

Tooltip message displayed while hovering a mouse over the widget when dark mode is available.

#### `darkModeTooltip` (string | HTMLElement | false, optional) {#darkmodetooltip}

* Default: value of `darkModeLabel`

Custom tooltip content when in dark mode. Overrides the default label text in the tooltip. Pass `false` to disable.

## Styles

| Name | Type | Default |
Expand Down
12 changes: 12 additions & 0 deletions docs/api-reference/widgets/timeline-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,18 @@ In controlled mode, the widget does not automatically reset time to the beginnin

Callback when play/pause button is clicked.

#### `playTooltip` (string | HTMLElement | false, optional) {#playtooltip}

* Default: `'Play'`

Custom tooltip content for the play button. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `pauseTooltip` (string | HTMLElement | false, optional) {#pausetooltip}

* Default: `'Pause'`

Custom tooltip content for the pause button. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `formatLabel` (function, optional) {#formatlabel}

```ts
Expand Down
12 changes: 12 additions & 0 deletions docs/api-reference/widgets/toggle-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,24 @@ Data URL used as the icon when the widget is checked.

Tooltip message displayed while hovering over the widget.

#### `tooltip` (string | HTMLElement | false, optional) {#tooltip}

* Default: value of `label`

Custom tooltip content. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `onLabel` (string, optional) {#onlabel}

* Default: same as `label`

Tooltip shown while the widget is checked.

#### `onTooltip` (string | HTMLElement | false, optional) {#ontooltip}

* Default: value of `onLabel`

Custom tooltip content when the widget is checked. Overrides the default onLabel text in the tooltip. Pass `false` to disable.

#### `color` (string, optional) {#color}

CSS color of the icon.
Expand Down
138 changes: 138 additions & 0 deletions docs/api-reference/widgets/tooltips.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Widget Tooltips

<img src="https://img.shields.io/badge/from-v9.5-green.svg?style=flat-square" alt="from v9.5" />

Built-in button widgets ship with styled tooltips that appear on hover and keyboard focus. These replace the slow native browser title tooltips with themed, customizable alternatives.

## Customizing Built-in Tooltips

### `label` vs `tooltip`

The `label` prop sets the button's accessible name (`aria-label`) and is always a plain string. By default, the tooltip displays the label text. The `tooltip` prop overrides only the visual tooltip — use it for rich HTML content, a different display string, or `false` to hide the tooltip while preserving accessibility.

### Overriding tooltip text

Each button widget accepts a tooltip prop that overrides the default label:

```ts
new ZoomWidget({ zoomInTooltip: 'Zoom In (Ctrl+Plus)' })
new FullscreenWidget({ enterTooltip: 'Go Fullscreen (F)' })
```

### Overriding with custom HTML

For rich content (e.g. keyboard shortcut badges), pass an `HTMLElement`:

```ts
const tip = document.createElement('span');
tip.innerHTML = 'Zoom In <kbd>⌘+</kbd>';
new ZoomWidget({ zoomInTooltip: tip })
```

### Disabling tooltips

Pass `false` to suppress the tooltip for a specific button:

```ts
new ZoomWidget({ zoomInTooltip: false })
new CompassWidget({ tooltip: false })
```

### Styling tooltips

Tooltip appearance inherits the widget theme via CSS variables. You can customize them globally or per-widget:

```css
.deck-widget {
--tooltip-max-width: 300px;
--tooltip-z-index: 2000;
}
```

| Name | Type | Default |
| ---- | ---- | ------- |
| `--tooltip-max-width` | [Dimension](https://developer.mozilla.org/en-US/docs/Web/CSS/dimension) | `240px` |
| `--tooltip-z-index` | Number | `1000` |

Tooltips also inherit the following [menu variables](./styling.md#menu): `--menu-background`, `--menu-shadow`, `--menu-backdrop-filter`, `--menu-text`.

## Writing Tooltips in Custom Widgets

### Using Preact (with \_Tooltip component)

If your custom widget uses Preact for rendering, import the `_Tooltip` component:

```tsx
import {_Tooltip as Tooltip} from '@deck.gl/widgets';
import {render} from 'preact';

class MyWidget extends Widget {
className = 'my-widget';
placement = 'top-left';

onRenderHTML(rootElement) {
render(
<Tooltip content="My tooltip text">
<button aria-label="My Action" onClick={...}>
Do thing
</button>
</Tooltip>,
rootElement
);
}
}
```

#### TooltipProps

| Prop | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| `content` | `string \| ComponentChildren` | — | Tooltip content to display |
Comment thread
chrisgervang marked this conversation as resolved.
| `placement` | `Placement` | `'right'` | Position relative to the trigger (uses [@floating-ui/dom](https://floating-ui.com/docs/computePosition#placement) placement values) |
| `children` | `ComponentChildren` | — | The trigger element |

### Without Preact (CSS class + your own logic)

For custom widgets using vanilla JS, React, or any other framework, implement your own show/hide behavior and apply the `.deck-widget-tooltip` CSS class to get consistent theming:

```ts
class MyWidget extends Widget {
className = 'my-widget';
placement = 'top-left';

onRenderHTML(rootElement) {
const btn = document.createElement('button');
btn.setAttribute('aria-label', 'My Action');
btn.setAttribute('aria-describedby', 'my-tooltip');

const tooltip = document.createElement('div');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

tooltip.id = 'my-tooltip';
tooltip.className = 'deck-widget-tooltip';
tooltip.setAttribute('role', 'tooltip');
tooltip.textContent = 'My Action';
tooltip.hidden = true;

btn.addEventListener('pointerenter', () => { tooltip.hidden = false; });
btn.addEventListener('pointerleave', () => { tooltip.hidden = true; });

rootElement.replaceChildren(btn, tooltip);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing all children seems a bit heavy handed?

}
}
```

The `.deck-widget-tooltip` class provides themed background, shadow, text color, font, border-radius, and max-width — matching the rest of the widget UI. You are responsible for:

- Positioning (consider [@floating-ui/dom](https://floating-ui.com/docs/getting-started) or CSS anchor positioning)
- Show/hide behavior (pointer events, focus, keyboard dismiss)

## Accessibility

Built-in widget tooltips follow these accessibility practices:

- Buttons use `aria-label` for screen reader announcements
- Tooltip elements have `role="tooltip"`
- Tooltips appear on keyboard focus
- Pressing `Escape` dismisses the tooltip

Custom widget authors should follow the same patterns.

12 changes: 12 additions & 0 deletions docs/api-reference/widgets/zoom-widget.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,24 @@ Zoom level delta applied by each button click.

Tooltip message displayed while hovering a mouse over the zoom in button.

#### `zoomInTooltip` (string | HTMLElement | false, optional) {#zoomintooltip}

* Default: value of `zoomInLabel`

Custom tooltip content for the zoom in button. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `zoomOutLabel` (string, optional) {#zoomoutlabel}

* Default: `'Zoom Out'`

Tooltip message displayed while hovering a mouse over the zoom out button.

#### `zoomOutTooltip` (string | HTMLElement | false, optional) {#zoomouttooltip}

* Default: value of `zoomOutLabel`

Custom tooltip content for the zoom out button. Overrides the default label text in the tooltip. Pass `false` to disable.

#### `transitionDuration` (number, optional) {#transitionduration}

* Default: `200`
Expand Down
1 change: 1 addition & 0 deletions docs/table-of-contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@
"items": [
"api-reference/widgets/overview",
"api-reference/widgets/styling",
"api-reference/widgets/tooltips",
"api-reference/widgets/compass-widget",
"api-reference/widgets/context-menu-widget",
"api-reference/widgets/fullscreen-widget",
Expand Down
Loading
Loading