Skip to content

feat(packages): compound tooltips with label and shortcut parts#1494

Merged
sampotts merged 1 commit into
mainfrom
feat/hotkey-tooltips
Jun 17, 2026
Merged

feat(packages): compound tooltips with label and shortcut parts#1494
sampotts merged 1 commit into
mainfrom
feat/hotkey-tooltips

Conversation

@sampotts

@sampotts sampotts commented Apr 30, 2026

Copy link
Copy Markdown
Collaborator

Closes #1299

Summary

Adds compound tooltips with separate label and keyboard-shortcut regions across HTML custom elements, React, and skins. Hotkey metadata flows through an extended coordinator (display shortcuts, optional action value, subscriber notifications). Playback rate uses core-owned copy and spinbutton semantics so tooltip and accessibility stay aligned on both platforms. Coordinator-driven shortcut updates no longer double-fire tooltip sync.

Screenshots

CleanShot 2026-06-17 at 16 32 58@2x CleanShot 2026-06-17 at 16 33 11@2x

Changes

  • HTML: media-tooltip-label and media-tooltip-shortcut; tooltip syncs label/shortcut from the trigger and can create default parts when markup is empty
  • Core: hotkey coordinator gains getShortcut, display formatting, subscribe/notify, and HOTKEY_SHORTCUT_CHANGE_EVENT; exports getHotkeyCoordinator and toDisplayKeyShortcut
  • React: Tooltip.Label / Tooltip.Shortcut, useHotkeyShortcut (replaces useAriaKeyShortcuts), tooltip context updates
  • Skins / presets: layout and tokens for shortcut affordance; default <media-hotkey> wiring in minimal skins where appropriate
  • AriaKeyShortcutsController: coordinator callback only schedules requestUpdate; MediaButtonElement continues to emit hotkey-shortcut-change when the display shortcut actually changes (avoids duplicate tooltip sync)
  • PlaybackRateButton (core): default label Playback speed for tooltip and aria-label; role="spinbutton" with aria-valuenow, aria-valuetext (e.g. 1.25×, same numeric string as valuenow), and aria-valuemin / aria-valuemax from the current playbackRates list when non-empty
  • React createMediaButton: merge order [getButtonProps(), elementProps, last attrs] so getAttrs() (spinbutton role and ARIA) wins over createButton’s default role="button"
  • Docs: playback rate reference page updated for the new accessibility model
  • Tests: tooltip sync, hotkey shortcut hook, coordinator, playback rate (core/HTML/React)
Implementation details
  • Shortcut updates: controls subscribe to the coordinator; MediaButtonElement.#syncHotkeyShortcut dedupes by last display shortcut string before dispatching HOTKEY_SHORTCUT_CHANGE_EVENT.
  • Rate strings for ARIA use a shared formatter (three-decimal rounding) so aria-valuenow and the numeric part of aria-valuetext stay identical.

Testing

  • pnpm typecheck
  • pnpm -F @videojs/core test src/core/ui/playback-rate-button src/dom/hotkey
  • pnpm -F @videojs/html test src/ui/tooltip src/ui/hotkey src/ui/playback-rate-button
  • pnpm -F @videojs/react test src/ui/

Note

Medium Risk
Refactors hotkey shortcut resolution and fans out through accessibility attributes and tooltip UI across HTML/React presets; behavior is covered by tests but registration-order and value-matching semantics affect what users see in tooltips and aria-keyshortcuts.

Overview
Tooltips now split into label and keyboard shortcut regions (media-tooltip-label / media-tooltip-shortcut in HTML; Tooltip.Label / Tooltip.Shortcut in React). Presets and skins wire those parts and style the shortcut (e.g. media-tooltip__kbd, popup.tooltipShortcut).

Hotkey metadata is unified in HotkeyCoordinator: the separate ARIA registry is removed in favor of getShortcut(action, value?), which returns both aria (toAriaKeyShortcut) and a compact shortcut (toDisplayKeyShortcut, e.g. Ctrl+K). Bindings can be filtered by action value (e.g. seek step seconds), disabled bindings are excluded, and the latest registration is preferred for display. subscribeShortcutChanges and HOTKEY_SHORTCUT_CHANGE_EVENT notify controls when registrations change.

Controls subscribe to that flow: AriaKeyShortcutsController / useHotkeyShortcut (replacing useAriaKeyShortcuts) refresh aria-keyshortcuts and display shortcuts; MediaButtonElement dedupes and dispatches shortcut-change events; tooltips sync label + shortcut from triggers (including getShortcut() on media buttons). Seek and playback rate buttons declare matching hotkey actions/values in HTML and React.

Tooltip popup styling adds flex layout when open so shortcut chips layout correctly without breaking closed [popover] hiding. Slider skin tokens pick up minor drag/focus-ring tweaks unrelated to tooltips.

Reviewed by Cursor Bugbot for commit 0682ce3. Bugbot is set up for automated code reviews on this repo. Configure here.

@netlify

netlify Bot commented Apr 30, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 0682ce3
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a3317ad91847400075189db
😎 Deploy Preview https://deploy-preview-1494--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

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

@vercel

vercel Bot commented Apr 30, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment Jun 17, 2026 9:55pm

Request Review

@github-actions

github-actions Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html

Path Base PR Diff %
/video/minimal-skin 43.31 kB 43.79 kB +495 B +1.1% 🔺
/video/minimal-skin.tailwind 43.79 kB 44.45 kB +674 B +1.5% 🔺
/video/skin 43.60 kB 44.17 kB +588 B +1.3% 🔺
/video/skin.tailwind 44.14 kB 44.74 kB +614 B +1.4% 🔺
/audio/minimal-skin 34.38 kB 36.56 kB +2.18 kB +6.3% 🔺
/audio/minimal-skin.tailwind 34.69 kB 36.91 kB +2.21 kB +6.4% 🔺
/audio/skin 37.15 kB 37.75 kB +616 B +1.6% 🔺
/audio/skin.tailwind 37.47 kB 38.09 kB +638 B +1.7% 🔺
/live-video/minimal-skin 42.41 kB 42.98 kB +578 B +1.3% 🔺
/live-video/minimal-skin.tailwind 42.73 kB 43.37 kB +658 B +1.5% 🔺
/live-video/skin 42.41 kB 42.92 kB +526 B +1.2% 🔺
/live-video/skin.tailwind 42.70 kB 43.40 kB +708 B +1.6% 🔺
/live-audio/minimal-skin 27.94 kB 29.62 kB +1.67 kB +6.0% 🔺
/live-audio/minimal-skin.tailwind 27.37 kB 29.08 kB +1.71 kB +6.2% 🔺
/live-audio/skin 30.42 kB 30.94 kB +530 B +1.7% 🔺
/live-audio/skin.tailwind 29.93 kB 30.52 kB +604 B +2.0% 🔺
/ui/airplay-button 3.11 kB 3.65 kB +546 B +17.1% 🔴
/ui/captions-button 3.18 kB 3.72 kB +552 B +16.9% 🔴
/ui/cast-button 3.10 kB 3.62 kB +537 B +16.9% 🔴
/ui/fullscreen-button 3.07 kB 3.63 kB +581 B +18.5% 🔴
/ui/mute-button 3.08 kB 3.66 kB +599 B +19.0% 🔴
/ui/pip-button 3.11 kB 3.64 kB +545 B +17.1% 🔴
/ui/play-button 3.08 kB 3.58 kB +513 B +16.3% 🔴
/ui/playback-rate-button 3.15 kB 3.78 kB +645 B +20.0% 🔴
/ui/seek-button 3.09 kB 3.63 kB +550 B +17.4% 🔴
/video (default) 43.59 kB 44.12 kB +539 B +1.2% 🔺
/video (default + hls) 183.07 kB 183.65 kB +592 B +0.3% 🔺
/video (minimal) 43.30 kB 43.89 kB +598 B +1.3% 🔺
/video (minimal + hls) 182.74 kB 183.28 kB +552 B +0.3% 🔺
/audio (default) 37.22 kB 37.71 kB +504 B +1.3% 🔺
/audio (minimal) 34.38 kB 36.54 kB +2.16 kB +6.3% 🔺
Presets (7)
Entry Size
/video (default) 44.12 kB
/video (default + hls) 183.65 kB
/video (minimal) 43.89 kB
/video (minimal + hls) 183.28 kB
/audio (default) 37.71 kB
/audio (minimal) 36.54 kB
/background 4.22 kB
Media (9)
Entry Size
/media/background-video 1.07 kB
/media/container 1.72 kB
/media/dash-video 242.77 kB
/media/hls-video 141.09 kB
/media/mux-audio 163.66 kB
/media/mux-video 163.48 kB
/media/native-hls-video 8.94 kB
/media/simple-hls-audio-only 16.85 kB
/media/simple-hls-video 18.67 kB
Players (5)
Entry Size
/video/player 8.06 kB
/audio/player 5.38 kB
/background/player 3.93 kB
/live-video/player 7.64 kB
/live-audio/player 5.39 kB
Skins (30)
Entry Type Size
/video/minimal-skin.css css 5.38 kB
/video/skin.css css 5.35 kB
/video/minimal-skin js 43.79 kB
/video/minimal-skin.tailwind js 44.45 kB
/video/skin js 44.17 kB
/video/skin.tailwind js 44.74 kB
/audio/minimal-skin.css css 3.60 kB
/audio/skin.css css 3.53 kB
/audio/minimal-skin js 36.56 kB
/audio/minimal-skin.tailwind js 36.91 kB
/audio/skin js 37.75 kB
/audio/skin.tailwind js 38.09 kB
/background/skin.css css 133 B
/background/skin js 1.16 kB
/live-video/minimal-skin.css css 5.38 kB
/live-video/skin.css css 5.35 kB
/live-video/minimal-skin js 42.98 kB
/live-video/minimal-skin.tailwind js 43.37 kB
/live-video/skin js 42.92 kB
/live-video/skin.tailwind js 43.40 kB
/live-audio/minimal-skin.css css 3.60 kB
/live-audio/skin.css css 3.53 kB
/live-audio/minimal-skin js 29.62 kB
/live-audio/minimal-skin.tailwind js 29.08 kB
/live-audio/skin js 30.94 kB
/live-audio/skin.tailwind js 30.52 kB
/global.css css 176 B
/shared.css css 88 B
/tailwind.css css 228 B
/skin-element js 1.37 kB
UI Components (38)
Entry Size
/ui/airplay-button 3.65 kB
/ui/alert-dialog 1.26 kB
/ui/alert-dialog-close 582 B
/ui/alert-dialog-description 418 B
/ui/alert-dialog-title 407 B
/ui/buffering-indicator 2.98 kB
/ui/captions-button 3.72 kB
/ui/captions-radio-group 3.43 kB
/ui/cast-button 3.62 kB
/ui/compounds 8.58 kB
/ui/controls 2.85 kB
/ui/error-dialog 3.45 kB
/ui/fullscreen-button 3.63 kB
/ui/hotkey 2.22 kB
/ui/menu 5.69 kB
/ui/mute-button 3.66 kB
/ui/pip-button 3.64 kB
/ui/play-button 3.58 kB
/ui/playback-rate-button 3.78 kB
/ui/playback-rate-radio-group 3.10 kB
/ui/popover 2.04 kB
/ui/poster 2.78 kB
/ui/quality-radio-group 2.49 kB
/ui/seek-button 3.63 kB
/ui/seek-indicator 3.92 kB
/ui/seek-indicator-value 232 B
/ui/slider 1.49 kB
/ui/status-announcer 3.58 kB
/ui/status-indicator 3.72 kB
/ui/status-indicator-value 265 B
/ui/thumbnail 3.33 kB
/ui/time 3.05 kB
/ui/time-slider 4.09 kB
/ui/tooltip 2.26 kB
/ui/volume-indicator 3.90 kB
/ui/volume-indicator-fill 209 B
/ui/volume-indicator-value 209 B
/ui/volume-slider 2.92 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react

Path Base PR Diff %
/video/minimal-skin 36.30 kB 36.63 kB +341 B +0.9% 🔺
/video/minimal-skin.tailwind 41.86 kB 42.37 kB +522 B +1.2% 🔺
/video/skin 36.21 kB 36.61 kB +413 B +1.1% 🔺
/video/skin.tailwind 41.76 kB 42.28 kB +536 B +1.3% 🔺
/audio/minimal-skin 29.58 kB 29.96 kB +389 B +1.3% 🔺
/audio/minimal-skin.tailwind 30.12 kB 31.75 kB +1.63 kB +5.4% 🔺
/audio/skin 29.54 kB 29.93 kB +398 B +1.3% 🔺
/audio/skin.tailwind 33.25 kB 33.77 kB +525 B +1.5% 🔺
/live-video/minimal-skin 32.10 kB 32.43 kB +343 B +1.0% 🔺
/live-video/minimal-skin.tailwind 37.52 kB 37.96 kB +457 B +1.2% 🔺
/live-video/skin 32.06 kB 32.44 kB +386 B +1.2% 🔺
/live-video/skin.tailwind 37.49 kB 37.98 kB +498 B +1.3% 🔺
/live-audio/minimal-skin 21.38 kB 21.74 kB +363 B +1.7% 🔺
/live-audio/minimal-skin.tailwind 24.14 kB 24.65 kB +525 B +2.1% 🔺
/live-audio/skin 21.41 kB 21.79 kB +387 B +1.8% 🔺
/live-audio/skin.tailwind 24.29 kB 24.76 kB +480 B +1.9% 🔺
/ui/airplay-button 2.38 kB 3.52 kB +1.14 kB +47.9% 🔴
/ui/captions-button 2.39 kB 3.55 kB +1.16 kB +48.5% 🔴
/ui/cast-button 2.38 kB 3.51 kB +1.13 kB +47.5% 🔴
/ui/fullscreen-button 3.12 kB 2.63 kB -500 B -15.7% 🔽
/ui/mute-button 2.44 kB 2.65 kB +219 B +8.8% 🔺
/ui/pip-button 3.05 kB 2.61 kB -452 B -14.5% 🔽
/ui/play-button 3.06 kB 2.65 kB -423 B -13.5% 🔽
/ui/playback-rate-button 2.41 kB 2.61 kB +206 B +8.4% 🔺
/ui/seek-button 2.41 kB 2.68 kB +279 B +11.3% 🔴
/video (default) 36.31 kB 36.68 kB +379 B +1.0% 🔺
/video (default + hls) 174.71 kB 175.02 kB +323 B +0.2% 🔺
/video (minimal) 36.38 kB 36.74 kB +365 B +1.0% 🔺
/video (minimal + hls) 174.64 kB 175.10 kB +471 B +0.3% 🔺
/audio (default) 29.63 kB 29.97 kB +339 B +1.1% 🔺
/audio (minimal) 29.69 kB 30.04 kB +358 B +1.2% 🔺
Presets (7)
Entry Size
/video (default) 36.68 kB
/video (default + hls) 175.02 kB
/video (minimal) 36.74 kB
/video (minimal + hls) 175.10 kB
/audio (default) 29.97 kB
/audio (minimal) 30.04 kB
/background 754 B
Media (8)
Entry Size
/media/background-video 575 B
/media/dash-video 241.38 kB
/media/hls-video 139.71 kB
/media/mux-audio 162.30 kB
/media/mux-video 162.27 kB
/media/native-hls-video 7.41 kB
/media/simple-hls-audio-only 15.40 kB
/media/simple-hls-video 17.20 kB
Skins (27)
Entry Type Size
/tailwind.css css 228 B
/video/minimal-skin.css css 5.30 kB
/video/skin.css css 5.26 kB
/video/minimal-skin js 36.63 kB
/video/minimal-skin.tailwind js 42.37 kB
/video/skin js 36.61 kB
/video/skin.tailwind js 42.28 kB
/audio/minimal-skin.css css 3.47 kB
/audio/skin.css css 3.39 kB
/audio/minimal-skin js 29.96 kB
/audio/minimal-skin.tailwind js 31.75 kB
/audio/skin js 29.93 kB
/audio/skin.tailwind js 33.77 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 5.30 kB
/live-video/skin.css css 5.26 kB
/live-video/minimal-skin js 32.43 kB
/live-video/minimal-skin.tailwind js 37.96 kB
/live-video/skin js 32.44 kB
/live-video/skin.tailwind js 37.98 kB
/live-audio/minimal-skin.css css 3.47 kB
/live-audio/skin.css css 3.39 kB
/live-audio/minimal-skin js 21.74 kB
/live-audio/minimal-skin.tailwind js 24.65 kB
/live-audio/skin js 21.79 kB
/live-audio/skin.tailwind js 24.76 kB
UI Components (32)
Entry Size
/ui/airplay-button 3.52 kB
/ui/alert-dialog 1.08 kB
/ui/buffering-indicator 2.05 kB
/ui/captions-button 3.55 kB
/ui/captions-radio-group 2.74 kB
/ui/cast-button 3.51 kB
/ui/controls 2.10 kB
/ui/error-dialog 2.65 kB
/ui/fullscreen-button 2.63 kB
/ui/gesture 2.17 kB
/ui/hotkey 2.05 kB
/ui/live-button 2.95 kB
/ui/menu 7.18 kB
/ui/mute-button 2.65 kB
/ui/pip-button 2.61 kB
/ui/play-button 2.65 kB
/ui/playback-rate 2.74 kB
/ui/playback-rate-button 2.61 kB
/ui/popover 2.29 kB
/ui/poster 2.33 kB
/ui/quality 2.95 kB
/ui/seek-button 2.68 kB
/ui/seek-indicator 2.08 kB
/ui/slider 3.65 kB
/ui/status-announcer 1.87 kB
/ui/status-indicator 2.06 kB
/ui/thumbnail 2.38 kB
/ui/time 2.94 kB
/ui/time-slider 4.14 kB
/ui/tooltip 2.64 kB
/ui/volume-indicator 2.06 kB
/ui/volume-slider 4.11 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core — no changes
Entries (11)
Entry Size
. 8.65 kB
/dom 17.01 kB
/dom/media/custom-media-element 2.00 kB
/dom/media/dash 236.85 kB
/dom/media/google-cast 4.04 kB
/dom/media/hls 135.71 kB
/dom/media/media-host 1.31 kB
/dom/media/mux 151.26 kB
/dom/media/native-hls 3.02 kB
/dom/media/simple-hls 16.56 kB
/dom/media/simple-hls-audio-only 14.74 kB
🏷️ @videojs/element — no changes
Entries (2)
Entry Size
. 996 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 2.22 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 192 B
/style 190 B
/time 478 B
/number 158 B
📦 @videojs/spf — no changes
Entries (4)
Entry Size
. 4.45 kB
/dom 6.32 kB
/hls 15.44 kB
/background-looping-video 12.95 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.

Comment thread packages/html/src/ui/hotkey/aria-key-shortcuts-controller.ts
Comment thread packages/react/src/presets/audio/minimal-skin.tailwind.tsx Outdated
Comment thread packages/core/src/core/ui/playback-rate-button/playback-rate-button-core.ts Outdated
Comment thread packages/core/src/core/ui/playback-rate-button/playback-rate-button-core.ts Outdated

@luwes luwes left a comment

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.

Scanned through it, quick test, lgtm!
New markup looks intuitive.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 using default effort and found 1 potential issue.

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit 74518ea. Configure here.

Comment thread packages/html/src/ui/media-button-element.ts Outdated
@sampotts sampotts force-pushed the feat/hotkey-tooltips branch from 74518ea to 0682ce3 Compare June 17, 2026 21:54
@sampotts sampotts merged commit 035b509 into main Jun 17, 2026
26 checks passed
@sampotts sampotts deleted the feat/hotkey-tooltips branch June 17, 2026 22:12
@luwes luwes mentioned this pull request Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Feature: Add Hotkey Shortcut to Button Tooltips

3 participants