Skip to content

fix(packages): make tooltips visual-only and auto-forward media button labels#1174

Merged
mihar-22 merged 5 commits intomainfrom
feat/tooltip-label-forwarding
Apr 2, 2026
Merged

fix(packages): make tooltips visual-only and auto-forward media button labels#1174
mihar-22 merged 5 commits intomainfrom
feat/tooltip-label-forwarding

Conversation

@mihar-22
Copy link
Copy Markdown
Member

@mihar-22 mihar-22 commented Apr 1, 2026

Closes #1070

Tooltips are now visual-only (role="presentation") with no ARIA relationship to the trigger — no aria-describedby, no aria-labelledby. Media buttons always self-label with aria-label, so the accessible name never depends on tooltip visibility. This eliminates the double screen-reader readout where both the button's aria-label and the tooltip's aria-describedby announced the same text.

The DX improvement of auto-forwarding labels is preserved: when a media button is inside a tooltip, it pushes its current label (e.g. "Play", "Pause") to the tooltip popup as default content. Skin authors no longer need manual label components like <PlayLabel /> — the tooltip content updates automatically as media state changes.

Approach informed by Base UI's tooltip philosophy: tooltips are supplementary visual hints for sighted mouse/keyboard users and should not carry critical accessibility information, since they're invisible to touch users and unreliable for screen readers.


Note

Medium Risk
Changes tooltip ARIA semantics and rewires how tooltip text is produced across HTML/React presets; regressions could affect accessibility expectations or leave some tooltips empty if a trigger doesn’t provide a label.

Overview
Tooltips are changed to be visual-only: TooltipCore.getTriggerAttrs() no longer sets aria-describedby, and getPopupAttrs() now uses role="presentation" (tests updated accordingly).

Tooltip content is now auto-derived from the trigger’s current label. HTML MediaButtonElement exposes getLabel() and TooltipElement copies that label into its text, while React adds content to tooltip context and createMediaButton() pushes core.getLabel(state) into the surrounding Tooltip.Root so Tooltip.Popup can render it as default children.

Default/minimal audio+video skins (HTML, React, and Tailwind) are updated to remove multi-state tooltip label markup and the shared tooltip-state CSS/Tailwind exports are deleted since label switching is no longer done via CSS selectors.

Written by Cursor Bugbot for commit 4ffd4fb. This will update automatically on new commits. Configure here.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 1, 2026

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 2f9a625
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/69cedb21d42c8700097df462
😎 Deploy Preview https://deploy-preview-1174--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment Apr 2, 2026 9:10pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

📦 Bundle Size Report

🎨 @videojs/html — no changes
Presets (7)
Entry Size
/video (default) 24.83 kB
/video (default + hls) 156.56 kB
/video (minimal) 24.76 kB
/video (minimal + hls) 156.38 kB
/audio (default) 22.94 kB
/audio (minimal) 22.97 kB
/background 6.86 kB
Media (7)
Entry Size
/media/background-video 1.04 kB
/media/container 1.59 kB
/media/dash-video 236.49 kB
/media/hls-video 133.17 kB
/media/mux-video 155.85 kB
/media/native-hls-video 3.03 kB
/media/simple-hls-video 14.98 kB
Players (3)
Entry Size
/video/player 6.57 kB
/audio/player 6.56 kB
/background/player 6.56 kB
Skins (17)
Entry Type Size
/video/minimal-skin.css css 3.41 kB
/video/skin.css css 3.44 kB
/video/minimal-skin js 23.95 kB
/video/minimal-skin.tailwind js 24.33 kB
/video/skin js 24.03 kB
/video/skin.tailwind js 24.38 kB
/audio/minimal-skin.css css 2.48 kB
/audio/skin.css css 2.45 kB
/audio/minimal-skin js 22.19 kB
/audio/minimal-skin.tailwind js 22.44 kB
/audio/skin js 22.14 kB
/audio/skin.tailwind js 22.45 kB
/background/skin.css css 117 B
/background/skin js 1.15 kB
/base.css css 157 B
/shared.css css 88 B
/skin-element js 1.34 kB
UI Components (22)
Entry Size
/ui/alert-dialog 1.89 kB
/ui/alert-dialog-close 1.63 kB
/ui/alert-dialog-description 1.62 kB
/ui/alert-dialog-title 1.63 kB
/ui/buffering-indicator 1.67 kB
/ui/captions-button 1.97 kB
/ui/controls 1.65 kB
/ui/error-dialog 2.07 kB
/ui/fullscreen-button 1.93 kB
/ui/mute-button 1.94 kB
/ui/pip-button 1.93 kB
/ui/play-button 1.98 kB
/ui/playback-rate-button 1.68 kB
/ui/popover 1.72 kB
/ui/poster 1.54 kB
/ui/seek-button 1.98 kB
/ui/slider 1.65 kB
/ui/thumbnail 1.94 kB
/ui/time 1.73 kB
/ui/time-slider 2.10 kB
/ui/tooltip 2.24 kB
/ui/volume-slider 2.32 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react

Path Base PR Diff %
/video/minimal-skin 19.47 kB 19.78 kB +314 B +1.6% 🔺
/video/minimal-skin.tailwind 22.95 kB 23.25 kB +315 B +1.3% 🔺
/audio/minimal-skin 16.29 kB 16.58 kB +306 B +1.8% 🔺
/audio/skin 16.20 kB 16.51 kB +316 B +1.9% 🔺
/audio/skin.tailwind 18.75 kB 19.06 kB +311 B +1.6% 🔺
/ui/captions-button 1.92 kB 1.88 kB -38 B -1.9% 🔽
/ui/fullscreen-button 2.32 kB 1.83 kB -505 B -21.3% 🔽
/ui/mute-button 2.34 kB 1.82 kB -538 B -22.4% 🔽
/ui/pip-button 2.32 kB 1.81 kB -531 B -22.3% 🔽
/ui/play-button 2.32 kB 1.80 kB -534 B -22.5% 🔽
/ui/playback-rate-button 1.90 kB 1.28 kB -640 B -32.9% 🔽
/ui/seek-button 2.34 kB 1.81 kB -539 B -22.5% 🔽
/video (default + hls) 151.39 kB 151.70 kB +318 B +0.2% 🔺
/audio (default) 16.27 kB 16.58 kB +320 B +1.9% 🔺
/audio (minimal) 16.36 kB 16.67 kB +323 B +1.9% 🔺
Presets (7)
Entry Size
/video (default) 19.79 kB
/video (default + hls) 151.70 kB
/video (minimal) 19.84 kB
/video (minimal + hls) 151.63 kB
/audio (default) 16.58 kB
/audio (minimal) 16.67 kB
/background 3.13 kB
Media (6)
Entry Size
/media/background-video 476 B
/media/dash-video 236.45 kB
/media/hls-video 133.36 kB
/media/mux-video 155.88 kB
/media/native-hls-video 2.96 kB
/media/simple-hls-video 14.98 kB
Skins (14)
Entry Type Size
/video/minimal-skin.css css 3.36 kB
/video/skin.css css 3.37 kB
/video/minimal-skin js 19.78 kB
/video/minimal-skin.tailwind js 23.25 kB
/video/skin js 19.72 kB
/video/skin.tailwind js 23.31 kB
/audio/minimal-skin.css css 2.38 kB
/audio/skin.css css 2.34 kB
/audio/minimal-skin js 16.58 kB
/audio/minimal-skin.tailwind js 19.06 kB
/audio/skin js 16.51 kB
/audio/skin.tailwind js 19.06 kB
/background/skin.css css 90 B
/background/skin js 272 B
UI Components (19)
Entry Size
/ui/alert-dialog 1.45 kB
/ui/buffering-indicator 1.16 kB
/ui/captions-button 1.88 kB
/ui/controls 1.15 kB
/ui/error-dialog 1.66 kB
/ui/fullscreen-button 1.83 kB
/ui/mute-button 1.82 kB
/ui/pip-button 1.81 kB
/ui/play-button 1.80 kB
/ui/playback-rate-button 1.28 kB
/ui/popover 2.82 kB
/ui/poster 1.05 kB
/ui/seek-button 1.81 kB
/ui/slider 2.42 kB
/ui/thumbnail 1.49 kB
/ui/time 1.87 kB
/ui/time-slider 2.83 kB
/ui/tooltip 1.89 kB
/ui/volume-slider 2.78 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core — no changes
Entries (8)
Entry Size
. 5.24 kB
/dom 8.84 kB
/dom/media/custom-media-element 1.81 kB
/dom/media/dash 235.77 kB
/dom/media/hls 132.64 kB
/dom/media/mux 155.33 kB
/dom/media/native-hls 2.38 kB
/dom/media/simple-hls 14.39 kB
🏷️ @videojs/element — no changes
Entries (2)
Entry Size
. 999 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Size
. 1.39 kB
/html 696 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Size
/array 104 B
/dom 1.53 kB
/events 319 B
/function 261 B
/object 247 B
/predicate 265 B
/string 148 B
/style 190 B
/time 478 B
/number 158 B
📦 @videojs/spf — no changes
Entries (3)
Entry Size
. 40 B
/dom 12.45 kB
/playback-engine 12.41 kB

ℹ️ How to interpret

All sizes are standalone totals (minified + brotli).

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current sizes.

@mihar-22 mihar-22 force-pushed the feat/tooltip-label-forwarding branch from 785933d to 85019be Compare April 1, 2026 07:14
@mihar-22 mihar-22 force-pushed the feat/tooltip-label-forwarding branch from 85019be to 77413ec Compare April 1, 2026 07:18
@mihar-22 mihar-22 force-pushed the feat/tooltip-label-forwarding branch from 77413ec to 8cf7ed9 Compare April 1, 2026 07:20
@mihar-22 mihar-22 force-pushed the feat/tooltip-label-forwarding branch from 8cf7ed9 to d3d43f1 Compare April 1, 2026 07:27
@mihar-22 mihar-22 force-pushed the feat/tooltip-label-forwarding branch from d3d43f1 to 781d64e Compare April 1, 2026 07:31
@mihar-22 mihar-22 force-pushed the feat/tooltip-label-forwarding branch from 781d64e to 987129a Compare April 1, 2026 07:40
@mihar-22 mihar-22 force-pushed the feat/tooltip-label-forwarding branch from 987129a to 3a0a2e4 Compare April 1, 2026 07:43
@mihar-22 mihar-22 changed the title feat(packages): add tooltip label forwarding for media buttons fix(packages): make tooltips visual-only and auto-forward media button labels Apr 1, 2026
@mihar-22 mihar-22 marked this pull request as ready for review April 1, 2026 08:45
@mihar-22 mihar-22 requested a review from sampotts April 1, 2026 08:52
@mihar-22 mihar-22 marked this pull request as draft April 1, 2026 08:52
@mihar-22
Copy link
Copy Markdown
Member Author

mihar-22 commented Apr 1, 2026

Moved this back to draft, few bugs to iron out.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread packages/html/src/ui/tooltip/tooltip-element.ts
Comment thread packages/html/src/ui/tooltip/tooltip-element.ts
Copy link
Copy Markdown
Collaborator

@sampotts sampotts left a comment

Choose a reason for hiding this comment

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

Nice clean up of all the label/tooltip state stuff 👍🏼

mihar-22 and others added 3 commits April 1, 2026 12:49
When a media button is inside a Tooltip.Root, its label is automatically
forwarded to the tooltip popup. The ARIA strategy switches from aria-label
(self-labelling) to aria-labelledby (tooltip provides the accessible name),
eliminating duplicate screen reader announcements and manual label components.

Review feedback applied:
- Rename setLabelledByTooltip → setSuppressLabel (framework-agnostic naming)
- Switch useEffect → useLayoutEffect (close first-render a11y timing gap)
- Use isUndefined check for #content (treat empty string as forwarded content)
- Add setSuppressLabel tests for all 7 button cores
- Add empty string content test for TooltipCore
- HTML parity: MediaButtonElement exposes getLabel/setSuppressLabel,
  TooltipElement auto-detects media button triggers and forwards labels,
  cleanup removes both aria-describedby and aria-labelledby

Closes #1070

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tooltips are now purely visual (role="presentation") with no ARIA
relationship to the trigger. Buttons always keep their own aria-label.
This eliminates the double screen-reader readout without coupling
tooltip and button accessibility concerns.

Label content is still auto-forwarded from media buttons to tooltip
popups — skin authors don't need manual label components.

Approach informed by Base UI's tooltip philosophy: tooltips are
supplementary visual hints that should not carry critical a11y info.

Closes #1070

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tooltip content is now auto-forwarded from media buttons, so manual
label components (PlayLabel, CaptionsLabel, etc.) and CSS-driven
tooltip-state visibility rules are no longer needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use a MutationObserver on the trigger element to re-read the label
when data attributes change (e.g. paused → playing). Also clear
textContent when the label becomes falsy instead of leaving stale text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Button cores now include `label` in their state via a `ButtonState` base
interface and expose it through a reactive `State<T>` container (following
the BufferingIndicatorCore pattern). MediaButtonElement exposes `$state`
for external subscription.

The HTML tooltip element replaces its MutationObserver with a proper
state subscription via `$state.subscribe()`, keeping tooltip content
in sync through the same reactive graph as all other state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mihar-22 mihar-22 force-pushed the feat/tooltip-label-forwarding branch from bebef08 to 2f9a625 Compare April 2, 2026 21:09
@mihar-22 mihar-22 marked this pull request as ready for review April 2, 2026 21:17
@mihar-22 mihar-22 merged commit 86cf3e8 into main Apr 2, 2026
20 checks passed
@mihar-22 mihar-22 deleted the feat/tooltip-label-forwarding branch April 2, 2026 21:20
@luwes luwes mentioned this pull request Apr 2, 2026
github-actions bot added a commit that referenced this pull request Apr 2, 2026
…ltips

Reflect API changes from #1174 where tooltips became visual-only
and button labels are auto-forwarded:

- Update tooltip.mdx accessibility section: tooltips no longer set
  aria-describedby or role="tooltip", now use role="presentation"
- Update tooltip.mdx HTML behavior section: remove mention of ARIA
  attribute management
- Update accessibility.mdx: clarify that button labels are
  auto-forwarded and tooltips are visual-only

Closes #1204

Co-authored-by: rahim <github-actions[bot]@users.noreply.github.com>
This was referenced Apr 11, 2026
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.

fix: button + tooltip a11y double-readout and label forwarding

2 participants