A composable, accessible custom video player with auto-hiding controls, keyboard shortcuts, picture-in-picture, fullscreen, and icon-library-agnostic icons.
Every icon in this component uses the <IconPlaceholder> pattern so the shadcn CLI will swap them to the consumer's preferred icon library (Lucide, Tabler, HugeIcons, Phosphor) at install time. Use the icon picker in the header to preview what the component looks like with each library.
pnpm dlx shadcn@latest add https://ui.ahmet.studio/r/video-player
import { VideoPlayer } from "@/components/ui/video-player"; export default function Example() { return ( <VideoPlayer src="/path/to/video.mp4" poster="/path/to/poster.jpg" className="aspect-video" /> ); }
ref to call .play(), .pause(), .seek(s), or grab the underlying <video>.| Key | Action |
|---|---|
Space / k | Play / pause |
← / → | Seek ±5 seconds |
↑ / ↓ | Volume up / down (5%) |
m | Toggle mute |
f | Toggle fullscreen |
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | required | Video source URL. |
poster | string | — | Poster image shown before playback. |
className | string | — | Applied to the <video> element. |
containerClassName | string | — | Applied to the outer container. |
autoHideControlsMs | number | 2500 | Idle timeout before controls auto-hide while playing. Set 0 to keep them visible. |
All other native <video> attributes (autoPlay, loop, muted, event handlers, etc.) are forwarded to the underlying element.
import { useRef } from "react"; import { VideoPlayer, type VideoPlayerHandle, } from "@/components/ui/video-player"; export function Controlled() { const ref = useRef<VideoPlayerHandle>(null); return ( <div className="flex flex-col gap-2"> <VideoPlayer ref={ref} src="/video.mp4" className="aspect-video" /> <div className="flex gap-2"> <button onClick={() => ref.current?.play()}>Play</button> <button onClick={() => ref.current?.pause()}>Pause</button> <button onClick={() => ref.current?.seek(30)}>Skip to 0:30</button> </div> </div> ); }
aria-label and a tooltip.ring token.The component is authored with <IconPlaceholder lucide="..." tabler="..." hugeicons="..." phosphor="..." />. When you install with npx shadcn@latest add, the shadcn CLI looks at your components.json iconLibrary setting and rewrites every placeholder to the chosen library's native component, replacing the import accordingly. No runtime cost is added on the consumer side.
Set your icon library in components.json:
{ "iconLibrary": "tabler" }
Then install — the imports and JSX will use @tabler/icons-react automatically.