Dependency-free modal video player with a chaptered timeline, hover-scrub preview, speed menu, picture-in-picture, and fullscreen. React component.
Dependency-free custom video player: modal overlay, chaptered timeline, hover-scrub preview, speed menu, picture-in-picture, fullscreen, keyboard shortcuts, and focus trapping.
The trigger button and the modal are the same component instance — render
<VideoPlayer /> wherever you want the "Open video player" button to appear,
and the modal overlay portals itself to a fixed position automatically. Pass
children to replace the default icon + label:
src is used for both the main video and, unless previewSrc is set, the
muted hover-scrub preview video. Only normal hosted MP4 files are supported —
this is not an HLS/.m3u8 player. If a target device needs an
iOS-optimized encode, pass iosSrc (and previewIosSrc if it differs from
previewSrc); the player swaps to it automatically on iPad, iPhone, and
touch-enabled Macs.
The video is lazy-loaded: nothing downloads until the trigger is hovered, focused, touched, or the player is opened.
Styling ships as a single injected stylesheet rather than Tailwind classes,
so the component stays a self-contained, dependency-free file — inject-once
guarded, so mounting multiple players on a page is safe. Override the look via
the className (trigger wrapper), triggerClassName, and playerClassName
props, or target the .media-01 / [data-media-01-open] selectors directly.
| Prop | Type | Default |
|---|---|---|
src? | string | - |
previewSrc? | string | src |
iosSrc? | string | - |
previewIosSrc? | string | - |
defaultSpeed? | number | 1 |
speedOptions? | number[] | [1, 1.25, 1.5, 1.75, 2] |
chapters? | VideoPlayerChapter[] | - |
children? | ReactNode | Video icon + "Open video player" |
dialogLabel? | string | "Custom video player" |
className? | string | - |
triggerClassName? | string | - |
playerClassName? | string | - |
| Prop | Type | Default |
|---|---|---|
start? | number | - |
title? | string | - |
The player renders as a role="dialog" overlay with aria-modal, traps
focus while open, restores focus to the trigger (or the element that had
focus before opening) on close, and returns focus into the dialog on open.
The seek timeline is a role="slider" with live aria-valuenow /
aria-valuetext. When prefers-reduced-motion: reduce is set, transitions
and the pulse/play-pause icon morph collapse to instant state changes instead
of animating.
Use VideoPlayer for a hosted MP4 walkthrough, product demo, or trailer that
deserves a focused, chrome-rich viewing experience — chapters for longer
recordings, hover-scrub preview for quick navigation, and PiP/fullscreen for
viewers who want to keep watching while doing something else. For streaming
playlists (.m3u8/HLS), this player is not a fit — it only handles normal
hosted video files.
Hero Carousel
Crossfading hero image slideshow with a progress pill, play/pause, and prev/next controls. A React + Motion component.
Cursor Bubble
A floating label that follows the cursor and elastic-pops into view over interactive targets, powered by GSAP. React.
Built by Axyl. A motion-first component registry for React.
Last updated: 9/14/2026
Help keep SoraLabs moving.
Every contribution helps us build and maintain open-source tools for the community.
Sponsor on GitHub