diff --git a/internal/design/ui/gesture.md b/internal/design/ui/gesture.md
index 8b634481..efde7ab0 100644
--- a/internal/design/ui/gesture.md
+++ b/internal/design/ui/gesture.md
@@ -34,20 +34,20 @@ The component accepts additional props as needed (e.g. `value` for seek offset,
```tsx
{/* Full surface gestures */}
-
-
+
+
{/* Regions */}
-
-
-
+
+
+
{/* Pointer filtering */}
-
-
+
+
{/* Callback region — full control over hit testing */}
- state.x > state.containerWidth / 2}
diff --git a/internal/design/ui/hotkey.md b/internal/design/ui/hotkey.md
index adc536ff..90c02e67 100644
--- a/internal/design/ui/hotkey.md
+++ b/internal/design/ui/hotkey.md
@@ -71,11 +71,11 @@ Each element declares one binding. To bind multiple keys to the same action, use
#### React
```tsx
-
-
-
-
-
+
+
+
+
+
```
`keys` (not `key`) avoids collision with React's reserved `key` prop.
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index f75ce4c6..7137efc0 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -39,12 +39,12 @@ export type { ControlsGroupProps } from './ui/controls/controls-group';
export type { ControlsRootProps } from './ui/controls/controls-root';
export { ErrorDialog, type ErrorDialogContextValue, useErrorDialogContext } from './ui/error-dialog';
export { FullscreenButton, type FullscreenButtonProps } from './ui/fullscreen-button/fullscreen-button';
-export { MediaGesture, type MediaGestureProps } from './ui/gesture/media-gesture';
+export { Gesture, type GestureProps, MediaGesture, type MediaGestureProps } from './ui/gesture/gesture';
export { type UseDoubleTapGestureOptions, useDoubleTapGesture } from './ui/gesture/use-doubletap-gesture';
export { type UseTapGestureOptions, useTapGesture } from './ui/gesture/use-tap-gesture';
export { useButton } from './ui/hooks/use-button';
export { useSlider } from './ui/hooks/use-slider';
-export { MediaHotkey, type MediaHotkeyProps } from './ui/hotkey/media-hotkey';
+export { Hotkey, type HotkeyProps, MediaHotkey, type MediaHotkeyProps } from './ui/hotkey/hotkey';
export { useAriaKeyShortcuts } from './ui/hotkey/use-aria-key-shortcuts';
export { type UseHotkeyOptions, useHotkey } from './ui/hotkey/use-hotkey';
export { MuteButton, type MuteButtonProps } from './ui/mute-button/mute-button';
diff --git a/packages/react/src/presets/audio/skin.tsx b/packages/react/src/presets/audio/skin.tsx
index 783abead..8dd3eb7e 100644
--- a/packages/react/src/presets/audio/skin.tsx
+++ b/packages/react/src/presets/audio/skin.tsx
@@ -11,7 +11,7 @@ import { cn } from '@videojs/utils/style';
import { type ComponentProps, forwardRef, type ReactNode } from 'react';
import { Container, usePlayer } from '@/player/context';
import { ErrorDialog } from '@/ui/error-dialog';
-import { MediaHotkey } from '@/ui/hotkey/media-hotkey';
+import { Hotkey } from '@/ui/hotkey/hotkey';
import { MuteButton } from '@/ui/mute-button';
import { PlayButton } from '@/ui/play-button';
import { PlaybackRateButton } from '@/ui/playback-rate-button';
@@ -158,20 +158,20 @@ export function AudioSkin(props: AudioSkinProps): ReactNode {
{/* Hotkeys */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/packages/react/src/presets/video/skin.tsx b/packages/react/src/presets/video/skin.tsx
index 965a0f60..387a6bfc 100644
--- a/packages/react/src/presets/video/skin.tsx
+++ b/packages/react/src/presets/video/skin.tsx
@@ -26,8 +26,8 @@ import { CastButton } from '@/ui/cast-button';
import { Controls } from '@/ui/controls';
import { ErrorDialog } from '@/ui/error-dialog';
import { FullscreenButton } from '@/ui/fullscreen-button';
-import { MediaGesture } from '@/ui/gesture/media-gesture';
-import { MediaHotkey } from '@/ui/hotkey/media-hotkey';
+import { Gesture } from '@/ui/gesture/gesture';
+import { Hotkey } from '@/ui/hotkey/hotkey';
import { MuteButton } from '@/ui/mute-button';
import { PiPButton } from '@/ui/pip-button';
import { PlayButton } from '@/ui/play-button';
@@ -248,30 +248,30 @@ export function VideoSkin(props: VideoSkinProps): ReactNode {
{/* Hotkeys */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{/* Gestures */}
-
-
-
-
-
+
+
+
+
+
);
}
diff --git a/packages/react/src/ui/gesture/media-gesture.tsx b/packages/react/src/ui/gesture/gesture.tsx
similarity index 76%
rename from packages/react/src/ui/gesture/media-gesture.tsx
rename to packages/react/src/ui/gesture/gesture.tsx
index eb8a7572..e65baef5 100644
--- a/packages/react/src/ui/gesture/media-gesture.tsx
+++ b/packages/react/src/ui/gesture/gesture.tsx
@@ -14,7 +14,7 @@ import { useEffect } from 'react';
import { useContainer, usePlayer } from '../../player/context';
-export interface MediaGestureProps {
+export interface GestureProps {
type: 'tap' | 'doubletap' | (string & {});
action: GestureActionName | (string & {});
value?: number;
@@ -23,7 +23,7 @@ export interface MediaGestureProps {
disabled?: boolean;
}
-export function MediaGesture({ type, action, value, pointer, region, disabled }: MediaGestureProps): ReactNode {
+export function Gesture({ type, action, value, pointer, region, disabled }: GestureProps): ReactNode {
const store = usePlayer() as AnyPlayerStore;
const container = useContainer();
@@ -48,3 +48,13 @@ export function MediaGesture({ type, action, value, pointer, region, disabled }:
return null;
}
+
+export namespace Gesture {
+ export type Props = GestureProps;
+}
+
+/** @deprecated Use `GestureProps` instead. */
+export type MediaGestureProps = GestureProps;
+
+/** @deprecated Use `Gesture` instead. */
+export const MediaGesture = Gesture;
diff --git a/packages/react/src/ui/hotkey/media-hotkey.tsx b/packages/react/src/ui/hotkey/hotkey.tsx
similarity index 73%
rename from packages/react/src/ui/hotkey/media-hotkey.tsx
rename to packages/react/src/ui/hotkey/hotkey.tsx
index 7d5cb2d1..e109a3e7 100644
--- a/packages/react/src/ui/hotkey/media-hotkey.tsx
+++ b/packages/react/src/ui/hotkey/hotkey.tsx
@@ -12,7 +12,7 @@ import { useEffect } from 'react';
import { useContainer, usePlayer } from '../../player/context';
-export interface MediaHotkeyProps {
+export interface HotkeyProps {
keys: string;
action: HotkeyActionName | (string & {});
value?: number;
@@ -20,7 +20,7 @@ export interface MediaHotkeyProps {
target?: 'player' | 'document';
}
-export function MediaHotkey({ keys, action, value, disabled, target }: MediaHotkeyProps): ReactNode {
+export function Hotkey({ keys, action, value, disabled, target }: HotkeyProps): ReactNode {
const store = usePlayer() as AnyPlayerStore;
const container = useContainer();
@@ -44,3 +44,13 @@ export function MediaHotkey({ keys, action, value, disabled, target }: MediaHotk
return null;
}
+
+export namespace Hotkey {
+ export type Props = HotkeyProps;
+}
+
+/** @deprecated Use `HotkeyProps` instead. */
+export type MediaHotkeyProps = HotkeyProps;
+
+/** @deprecated Use `Hotkey` instead. */
+export const MediaHotkey = Hotkey;
diff --git a/site/src/content/docs/reference/player-container.mdx b/site/src/content/docs/reference/player-container.mdx
index 6d1091f6..f6ed8a84 100644
--- a/site/src/content/docs/reference/player-container.mdx
+++ b/site/src/content/docs/reference/player-container.mdx
@@ -122,8 +122,8 @@ Media discovery is handled by the provider, not the container. Custom media elem
The container is where user intent enters the player. It listens for physical interaction on its surface and translates that into player behavior:
- **User activity** — Mouse movement, touch, and keyboard activity within the container drive idle detection. This is how controls know when to show and hide.
-- **Gestures** *(coming soon)* — Click-to-play, double-click fullscreen, swipe to seek, and other touch/mouse gestures.
-- **Keyboard controls** *(coming soon)* — Spacebar to play/pause, arrow keys to seek, and other keyboard shortcuts scoped to the container.
+- **Gestures** — Click-to-play, double-click fullscreen, swipe to seek, and other touch/mouse gestures. Configured via the `Gesture` component.
+- **Keyboard controls** — Spacebar to play/pause, arrow keys to seek, and other keyboard shortcuts scoped to the container. Configured via the `Hotkey` component.
## Relationship to skins