Tooltip
A tooltip component for displaying contextual labels on hover and focus
Import
import { Tooltip } from '@videojs/react';import '@videojs/html/ui/tooltip';Anatomy
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>Hover me</Tooltip.Trigger>
<Tooltip.Popup>
<Tooltip.Arrow />
<Tooltip.Label>Label text</Tooltip.Label>
<Tooltip.Shortcut>K</Tooltip.Shortcut>
</Tooltip.Popup>
</Tooltip.Root>
</Tooltip.Provider><media-tooltip-group>
<button commandfor="my-tooltip">Hover me</button>
<media-tooltip id="my-tooltip">
<media-tooltip-label>Label text</media-tooltip-label>
<media-tooltip-shortcut>K</media-tooltip-shortcut>
</media-tooltip>
</media-tooltip-group>Behavior
Displays a short label anchored to a trigger element. Opens after a configurable delay (default 600ms) on hover or immediately on focus. Closes when the pointer leaves or focus moves away, with an optional closeDelay.
The side and align props control the preferred placement relative to the trigger. When the preferred side overflows the positioning boundary, the tooltip uses the opposite side if it has more space. Positioning uses CSS Anchor Positioning where supported, with a JavaScript measurement fallback.
The component is composed from six parts: Root manages state and context,
Trigger renders a button that activates the tooltip, Popup contains the label content,
Label renders the tooltip body, Shortcut renders an optional keyboard hint, and
Arrow renders a decorative pointer. Wrap multiple tooltips in a Tooltip.Provider
to coordinate open/close timing across a group — once a tooltip becomes visible, adjacent
tooltips open instantly within the timeout window, skipping the normal delay.
The <media-tooltip> element is the popup itself. Link it to a trigger using
the commandfor attribute on any button, pointing to the tooltip’s id. The element
discovers its trigger automatically and manages open/close state and positioning. Wrap
tooltip trigger/popup pairs in <media-tooltip-group> to coordinate
timing — the group’s delay, close-delay, and timeout attributes control shared
timing for all contained tooltips.
Tooltips inside a Container also observe its popup group. By default, a tooltip does not open—or closes if already open—while a menu or popover attached to the same trigger is open. Set sticky when the tooltip should remain visible with that trigger’s popup. Tooltip.Provider and <media-tooltip-group> coordinate tooltip delay timing only; they do not replace the container’s popup group.
Styling
Use CSS custom properties for positioning offsets:
media-tooltip {
--media-tooltip-side-offset: 8px;
--media-tooltip-align-offset: 0px;
--media-tooltip-boundary-offset: 8px;
}React renders standard DOM elements. Add a className to style them:
.tooltip-popup {
--media-tooltip-side-offset: 8px;
--media-tooltip-align-offset: 0px;
--media-tooltip-boundary-offset: 8px;
}Style based on open state, rendered side, and transition phases. data-side reflects the rendered side and can differ from the preferred side prop after collision handling:
media-tooltip[data-open] {
display: block;
}
media-tooltip[data-starting-style] {
opacity: 0;
}
media-tooltip[data-ending-style] {
opacity: 0;
}
media-tooltip[data-side="top"] {
transform-origin: bottom center;
}
media-tooltip[data-side="bottom"] {
transform-origin: top center;
}.tooltip-popup[data-open] {
display: block;
}
.tooltip-popup[data-starting-style] {
opacity: 0;
}
.tooltip-popup[data-ending-style] {
opacity: 0;
}
.tooltip-popup[data-side="top"] {
transform-origin: bottom center;
}
.tooltip-popup[data-side="bottom"] {
transform-origin: top center;
}Accessibility
Tooltips are visual-only, supplementary labels. The popup renders with role="presentation" and is not referenced by the trigger with aria-describedby. Give the trigger its own accessible name instead of relying on tooltip content. Built-in media buttons already expose a state-aware aria-label, and their tooltips reuse that translated label. Tooltips still open on focus so keyboard users who can see the label receive the same visual hint.
Examples
Basic Usage
import { Tooltip } from '@videojs/react';
export default function BasicUsage() {
return (
<div className="demo">
<Tooltip.Root>
<Tooltip.Trigger className="trigger">Hover me</Tooltip.Trigger>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Arrow className="arrow" />
<Tooltip.Label>Tooltip content</Tooltip.Label>
</Tooltip.Popup>
</Tooltip.Root>
</div>
);
}
.demo {
display: flex;
align-items: center;
justify-content: center;
padding: 40px 24px;
}
.trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.media-tooltip {
--media-tooltip-side-offset: 8px;
padding: 4px 10px;
margin: 0;
font-size: 13px;
color: white;
white-space: nowrap;
pointer-events: none;
background: rgba(0, 0, 0, 0.85);
border: 0;
border-radius: 6px;
backdrop-filter: blur(10px);
}
.arrow {
fill: rgba(0, 0, 0, 0.85);
}
<div class="demo">
<button type="button" commandfor="tooltip-demo" class="trigger">Hover me</button>
<media-tooltip id="tooltip-demo" class="media-tooltip"> Tooltip content </media-tooltip>
</div>
.demo {
display: flex;
align-items: center;
justify-content: center;
padding: 40px 24px;
}
.trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.media-tooltip {
--media-tooltip-side-offset: 8px;
padding: 4px 10px;
font-size: 13px;
color: white;
white-space: nowrap;
pointer-events: none;
background: rgba(0, 0, 0, 0.85);
border-radius: 6px;
backdrop-filter: blur(10px);
}
import '@videojs/html/ui/tooltip';
Grouping
Wrap multiple tooltips in a Tooltip.Provider to share a delay group. Once a tooltip
becomes visible, adjacent tooltips open instantly within the timeout window, skipping
the normal delay.
import { Tooltip } from '@videojs/react';
export default function Grouping() {
return (
<div className="demo">
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger className="trigger">Play</Tooltip.Trigger>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Arrow className="arrow" />
<Tooltip.Label>Play video</Tooltip.Label>
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger className="trigger">Mute</Tooltip.Trigger>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Arrow className="arrow" />
<Tooltip.Label>Mute audio</Tooltip.Label>
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger className="trigger">Fullscreen</Tooltip.Trigger>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Arrow className="arrow" />
<Tooltip.Label>Enter fullscreen</Tooltip.Label>
</Tooltip.Popup>
</Tooltip.Root>
</Tooltip.Provider>
</div>
);
}
.demo {
display: flex;
align-items: center;
justify-content: center;
padding: 40px 24px;
}
.trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
}
.media-tooltip {
--media-tooltip-side-offset: 8px;
padding: 4px 10px;
margin: 0;
font-size: 13px;
color: white;
white-space: nowrap;
pointer-events: none;
background: rgba(0, 0, 0, 0.85);
border: 0;
border-radius: 6px;
backdrop-filter: blur(10px);
}
.arrow {
fill: rgba(0, 0, 0, 0.85);
}
Wrap tooltip trigger/popup pairs in <media-tooltip-group> to coordinate timing. The
group’s delay, close-delay, and timeout attributes control shared timing for all
contained tooltips.
<media-tooltip-group class="demo">
<button type="button" commandfor="tooltip-play" class="trigger">Play</button>
<media-tooltip id="tooltip-play" class="media-tooltip">Play video</media-tooltip>
<button type="button" commandfor="tooltip-mute" class="trigger">Mute</button>
<media-tooltip id="tooltip-mute" class="media-tooltip">Mute audio</media-tooltip>
<button type="button" commandfor="tooltip-fullscreen" class="trigger">Fullscreen</button>
<media-tooltip id="tooltip-fullscreen" class="media-tooltip">Enter fullscreen</media-tooltip>
</media-tooltip-group>
.demo {
display: flex;
align-items: center;
justify-content: center;
padding: 40px 24px;
}
.trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
}
.media-tooltip {
--media-tooltip-side-offset: 8px;
padding: 4px 10px;
font-size: 13px;
color: white;
white-space: nowrap;
pointer-events: none;
background: rgba(0, 0, 0, 0.85);
border-radius: 6px;
backdrop-filter: blur(10px);
}
import '@videojs/html/ui/tooltip';
import '@videojs/html/ui/tooltip-group';
API Reference
Providermedia-tooltip-group
Rootmedia-tooltip
Props
| Prop | Type | Default | Details |
|---|---|---|---|
align | 'start' | 'center' | 'end' | 'center' | |
| |||
boundary | 'viewport' | 'container' | string & o... | — | |
| |||
closeDelay | number | 0 | |
| |||
defaultOpen | boolean | false | |
| |||
delay | number | 600 | |
| |||
disabled | boolean | false | |
| |||
disableHoverablePopup | boolean | true | |
| |||
open | boolean | false | |
| |||
side | 'top' | 'bottom' | 'left' | 'right' | 'top' | |
| |||
sticky | boolean | false | |
| |||
State
render, className, and style props.| Property | Type | Details |
|---|---|---|
transitionStarting | boolean | |
| ||
transitionEnding | boolean | |
| ||
open | boolean | |
| ||
status | 'idle' | 'starting' | 'ending' | |
| ||
side | 'top' | 'bottom' | 'left' | 'right' | |
| ||
align | 'start' | 'center' | 'end' | |
| ||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-open | ||
| ||
data-side | 'top' | 'bottom' | 'left' | 'right' | |
| ||
data-align | 'start' | 'center' | 'end' | |
| ||
CSS custom properties
| Variable | Details |
|---|---|
--media-tooltip-side-offset | |
| |
--media-tooltip-align-offset | |
| |
--media-tooltip-boundary-offset | |
| |
--media-tooltip-anchor-width | |
| |
--media-tooltip-anchor-height | |
| |
--media-tooltip-available-width | |
| |
--media-tooltip-available-height | |
| |
Events
| Event | Description |
|---|---|
open-change | Fired when the tooltip's open state changes. |
TriggerTrigger
Element that triggers the tooltip on hover and focus. Renders a <button> element.
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: TooltipState) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: TooltipState) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: TooltipState) => CSSProperties | undefined) | — | |
| |||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-open | ||
| ||
data-side | 'top' | 'bottom' | 'left' | 'right' | |
| ||
data-align | 'start' | 'center' | 'end' | |
| ||
PopupPopup
Container for the tooltip content. Positioned relative to the trigger using CSS anchor positioning with a JavaScript fallback.
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: TooltipState) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: TooltipState) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: TooltipState) => CSSProperties | undefined) | — | |
| |||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-open | ||
| ||
data-side | 'top' | 'bottom' | 'left' | 'right' | |
| ||
data-align | 'start' | 'center' | 'end' | |
| ||
Labelmedia-tooltip-label
Tooltip body label; defaults to context content.label from the linked trigger.
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: TooltipState) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: TooltipState) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: TooltipState) => CSSProperties | undefined) | — | |
| |||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-open | ||
| ||
data-side | 'top' | 'bottom' | 'left' | 'right' | |
| ||
data-align | 'start' | 'center' | 'end' | |
| ||
Shortcutmedia-tooltip-shortcut
Keyboard shortcut hint; apply skin className (CSS: media-tooltip__kbd; Tailwind: popup.tooltipShortcut).
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: TooltipState) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: TooltipState) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: TooltipState) => CSSProperties | undefined) | — | |
| |||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-open | ||
| ||
data-side | 'top' | 'bottom' | 'left' | 'right' | |
| ||
data-align | 'start' | 'center' | 'end' | |
| ||
ArrowArrow
Decorative arrow pointing from the tooltip toward the trigger. Hidden from assistive technology.
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: TooltipState) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: TooltipState) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: TooltipState) => CSSProperties | undefined) | — | |
| |||