diff --git a/docs/api-reference/widgets/compass-widget.md b/docs/api-reference/widgets/compass-widget.md
index ded087d4eb7..2a1eff9b759 100644
--- a/docs/api-reference/widgets/compass-widget.md
+++ b/docs/api-reference/widgets/compass-widget.md
@@ -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`
diff --git a/docs/api-reference/widgets/fullscreen-widget.md b/docs/api-reference/widgets/fullscreen-widget.md
index f50b2b8c04b..4aabe61195b 100644
--- a/docs/api-reference/widgets/fullscreen-widget.md
+++ b/docs/api-reference/widgets/fullscreen-widget.md
@@ -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
diff --git a/docs/api-reference/widgets/gimbal-widget.md b/docs/api-reference/widgets/gimbal-widget.md
index 569cb70b297..b4784109c9c 100644
--- a/docs/api-reference/widgets/gimbal-widget.md
+++ b/docs/api-reference/widgets/gimbal-widget.md
@@ -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`
diff --git a/docs/api-reference/widgets/icon-widget.md b/docs/api-reference/widgets/icon-widget.md
index 3770d9ca0dc..87735c731fd 100644
--- a/docs/api-reference/widgets/icon-widget.md
+++ b/docs/api-reference/widgets/icon-widget.md
@@ -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.
diff --git a/docs/api-reference/widgets/loading-widget.md b/docs/api-reference/widgets/loading-widget.md
index cf55a158f71..08b480ce86e 100644
--- a/docs/api-reference/widgets/loading-widget.md
+++ b/docs/api-reference/widgets/loading-widget.md
@@ -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}
diff --git a/docs/api-reference/widgets/overview.md b/docs/api-reference/widgets/overview.md
index cff3aacdc91..d1557a8c5bc 100644
--- a/docs/api-reference/widgets/overview.md
+++ b/docs/api-reference/widgets/overview.md
@@ -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).
diff --git a/docs/api-reference/widgets/reset-view-widget.md b/docs/api-reference/widgets/reset-view-widget.md
index 85bc8d9d95d..d3f71c842a2 100644
--- a/docs/api-reference/widgets/reset-view-widget.md
+++ b/docs/api-reference/widgets/reset-view-widget.md
@@ -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`
diff --git a/docs/api-reference/widgets/screenshot-widget.md b/docs/api-reference/widgets/screenshot-widget.md
index 12d9ed3aa6b..9c81f998aef 100644
--- a/docs/api-reference/widgets/screenshot-widget.md
+++ b/docs/api-reference/widgets/screenshot-widget.md
@@ -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'`
diff --git a/docs/api-reference/widgets/selector-widget.md b/docs/api-reference/widgets/selector-widget.md
index 714ff12f562..7b339c56381 100644
--- a/docs/api-reference/widgets/selector-widget.md
+++ b/docs/api-reference/widgets/selector-widget.md
@@ -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
diff --git a/docs/api-reference/widgets/styling.md b/docs/api-reference/widgets/styling.md
index 5a773a213ec..b72ab96adc6 100644
--- a/docs/api-reference/widgets/styling.md
+++ b/docs/api-reference/widgets/styling.md
@@ -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 |
diff --git a/docs/api-reference/widgets/theme-widget.md b/docs/api-reference/widgets/theme-widget.md
index 6ee3753a7d6..9fc8eab8538 100644
--- a/docs/api-reference/widgets/theme-widget.md
+++ b/docs/api-reference/widgets/theme-widget.md
@@ -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 |
diff --git a/docs/api-reference/widgets/timeline-widget.md b/docs/api-reference/widgets/timeline-widget.md
index 71c4247db03..f20a0d5d487 100644
--- a/docs/api-reference/widgets/timeline-widget.md
+++ b/docs/api-reference/widgets/timeline-widget.md
@@ -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
diff --git a/docs/api-reference/widgets/toggle-widget.md b/docs/api-reference/widgets/toggle-widget.md
index 76cc3478e62..94434f69555 100644
--- a/docs/api-reference/widgets/toggle-widget.md
+++ b/docs/api-reference/widgets/toggle-widget.md
@@ -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.
diff --git a/docs/api-reference/widgets/tooltips.md b/docs/api-reference/widgets/tooltips.md
new file mode 100644
index 00000000000..e51348fbb96
--- /dev/null
+++ b/docs/api-reference/widgets/tooltips.md
@@ -0,0 +1,138 @@
+# Widget Tooltips
+
+
+
+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 ⌘+';
+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(
+
+
+ ,
+ rootElement
+ );
+ }
+}
+```
+
+#### TooltipProps
+
+| Prop | Type | Default | Description |
+| ---- | ---- | ------- | ----------- |
+| `content` | `string \| ComponentChildren` | — | Tooltip content to display |
+| `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');
+ 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);
+ }
+}
+```
+
+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.
+
diff --git a/docs/api-reference/widgets/zoom-widget.md b/docs/api-reference/widgets/zoom-widget.md
index b0eae3f1c92..1bd48ecaf54 100644
--- a/docs/api-reference/widgets/zoom-widget.md
+++ b/docs/api-reference/widgets/zoom-widget.md
@@ -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`
diff --git a/docs/table-of-contents.json b/docs/table-of-contents.json
index a339e73cfc1..cb8272b2ffd 100644
--- a/docs/table-of-contents.json
+++ b/docs/table-of-contents.json
@@ -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",
diff --git a/modules/widgets/src/compass-widget.tsx b/modules/widgets/src/compass-widget.tsx
index 547b1280b1c..2d3971e62ab 100644
--- a/modules/widgets/src/compass-widget.tsx
+++ b/modules/widgets/src/compass-widget.tsx
@@ -5,6 +5,7 @@
import {Widget, FlyToInterpolator, WebMercatorViewport, _GlobeViewport} from '@deck.gl/core';
import type {Viewport, WidgetPlacement, WidgetProps} from '@deck.gl/core';
import {render} from 'preact';
+import {Tooltip} from './lib/components/tooltip';
export type CompassWidgetProps = WidgetProps & {
/** Widget positioning within the view. Default 'top-left'. */
@@ -13,6 +14,8 @@ export type CompassWidgetProps = WidgetProps & {
viewId?: string | null;
/** Tooltip message. */
label?: string;
+ /** Custom tooltip content. Overrides label for tooltip display. */
+ tooltip?: string | HTMLElement | false;
/** Bearing and pitch reset transition duration in ms. */
transitionDuration?: number;
/**
@@ -36,6 +39,7 @@ export class CompassWidget extends Widget {
placement: 'top-left',
viewId: null,
label: 'Reset Compass',
+ tooltip: undefined!,
transitionDuration: 200,
onReset: () => {}
};
@@ -60,31 +64,35 @@ export class CompassWidget extends Widget {
const widgetViewport = this.viewports[viewId];
const [rz, rx] = this.getRotation(widgetViewport);
+ const tooltipContent =
+ this.props.tooltip === false ? undefined : (this.props.tooltip ?? this.props.label);
const ui = (
-
+
+
+
);
diff --git a/modules/widgets/src/fullscreen-widget.tsx b/modules/widgets/src/fullscreen-widget.tsx
index 43cfae69a37..2cfb268d87f 100644
--- a/modules/widgets/src/fullscreen-widget.tsx
+++ b/modules/widgets/src/fullscreen-widget.tsx
@@ -19,6 +19,10 @@ export type FullscreenWidgetProps = WidgetProps & {
enterLabel?: string;
/** Tooltip message when fullscreen. */
exitLabel?: string;
+ /** Custom tooltip content when out of fullscreen. Overrides enterLabel for tooltip display. */
+ enterTooltip?: string | HTMLElement | false;
+ /** Custom tooltip content when fullscreen. Overrides exitLabel for tooltip display. */
+ exitTooltip?: string | HTMLElement | false;
/**
* A compatible DOM element which should be made full screen. By default, the map container element will be made full screen.
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen#Compatible_elements
@@ -38,6 +42,8 @@ export class FullscreenWidget extends Widget {
viewId: null,
enterLabel: 'Enter Fullscreen',
exitLabel: 'Exit Fullscreen',
+ enterTooltip: undefined!,
+ exitTooltip: undefined!,
container: undefined!,
onFullscreenChange: () => {}
};
@@ -67,6 +73,7 @@ export class FullscreenWidget extends Widget {
this.handleClick().catch(err => log.error(err)());
}}
label={isFullscreen ? this.props.exitLabel : this.props.enterLabel}
+ tooltip={isFullscreen ? this.props.exitTooltip : this.props.enterTooltip}
className={isFullscreen ? 'deck-widget-fullscreen-exit' : 'deck-widget-fullscreen-enter'}
/>,
rootElement
diff --git a/modules/widgets/src/gimbal-widget.tsx b/modules/widgets/src/gimbal-widget.tsx
index e53442dcbcd..1bd2ac9f13d 100644
--- a/modules/widgets/src/gimbal-widget.tsx
+++ b/modules/widgets/src/gimbal-widget.tsx
@@ -5,6 +5,7 @@
import {Widget, LinearInterpolator} from '@deck.gl/core';
import type {Viewport, WidgetPlacement, WidgetProps} from '@deck.gl/core';
import {render} from 'preact';
+import {Tooltip} from './lib/components/tooltip';
export type GimbalWidgetProps = WidgetProps & {
placement?: WidgetPlacement;
@@ -12,6 +13,8 @@ export type GimbalWidgetProps = WidgetProps & {
viewId?: string | null;
/** Tooltip message. */
label?: string;
+ /** Custom tooltip content. Overrides label for tooltip display. */
+ tooltip?: string | HTMLElement | false;
/** Width of gimbal lines. */
strokeWidth?: number;
/** Transition duration in ms when resetting rotation. */
@@ -37,6 +40,7 @@ export class GimbalWidget extends Widget {
placement: 'top-left',
viewId: null,
label: 'Gimbal',
+ tooltip: undefined!,
strokeWidth: 1.5,
transitionDuration: 200,
onReset: () => {}
@@ -62,64 +66,68 @@ export class GimbalWidget extends Widget {
const widgetViewport = this.viewports[viewId];
const {rotationOrbit, rotationX} = this.getNormalizedRotation(widgetViewport);
// Note - we use CSS 3D transforms instead of SVG 2D transforms
+ const tooltipContent =
+ this.props.tooltip === false ? undefined : (this.props.tooltip ?? this.props.label);
const ui = (
-
+ {/* Outer ring */}
+
+
+ {/* Inner ring */}
+
+
+