{"schemaVersion":"1.0","type":"Article","slug":"the-player-the-pieces-and-the-hook-gqg6e","url":"https://api.zyvop.com/the-player-the-pieces-and-the-hook-gqg6e","title":"The Player, the Pieces, and the Hook","subtitle":null,"tldr":"A Mantine-native video player for React that ships as three layers in one package: a polished...","keywords":["react","reactjsdevelopment","TypeScript","webcomponents"],"entities":["Giovambattista Fazioli","Senior Full-stack Engineer, Lead Developer","react","reactjsdevelopment","TypeScript","webcomponents","ZyVOP"],"keyTakeaways":["A Mantine-native video player for React that ships as three layers in one package: a polished default &lt;Video /&gt;, a composable compound API for the control bar, and a fully headless useVideo hook.","Introduction Imagine you're building a landing page.","The hero needs a looping background video."],"headings":["Introduction","What is mantine-video?","✨ Key Features","A polished default player","Compound API for custom control bars","Headless useVideo hook","Live timeline scrubbing","Four built-in variants","Background video — one prop turns the player into a hero","Picture-in-Picture","Keyboard shortcuts","🚀 Getting Started","Props &amp; API","🎨 Styling","Live Demo &amp; Documentation","Links"],"outboundLinks":["https://mantine.dev/styles/styles-api/","https://gfazioli.github.io/mantine-video","https://www.npmjs.com/package/@gfazioli/mantine-video","https://github.com/gfazioli/mantine-video","https://mantine-extensions.vercel.app/"],"contentText":"A Mantine-native video player for React that ships as three layers in one package: a polished default &lt;Video /&gt;, a composable compound API for the control bar, and a fully headless useVideo hook. Introduction Imagine you're building a landing page. The hero needs a looping background video. The product section needs an inline player with a clean control bar. And somewhere on the page, there's a feature demo where you want a completely custom UI — circular play button, vertical timeline, custom seek preview. With the native HTML &lt;video&gt; element, that's three different implementations, each with its own quirks and audio glitches. With @gfazioli/mantine-video, it's one component, three patterns, zero compromises. What is mantine-video? @gfazioli/mantine-video is a video player for React applications built with Mantine 9 and React 19. It wraps the native HTML &lt;video&gt; element with three layers of abstraction, layered from \"drop-in\" to \"fully custom\": &lt;Video /&gt; — a fully themed, batteries-included player that works out of the box. A compound API (Video.Controls, Video.PlayButton, Video.Timeline, …) for composable control bars. useVideo headless hook — full state and actions for building a 100% custom UI on top of a plain &lt;video&gt; element. Every part is theme-aware (color scheme, Mantine theme colors, radii, sizes), accessible (ARIA labels, keyboard shortcuts, focus management), and customizable through the full Mantine Styles API. Four variants — overlay, minimal, floating, bordered — cover the most common layouts out of the box. Picture-in-Picture, fullscreen, captions and live timeline scrubbing are wired in by default. Oh, and the same player can also be a background video. More on that below. ✨ Key Features A polished default player The fastest path: pass src, optionally poster and aspectRatio, and you have a complete player with play/pause, scrubbable timeline, time display, volume control, captions toggle, Picture-in-Picture and fullscreen. import { Video } from '@gfazioli/mantine-video'; function Demo() {return ( &lt;Video src=\"https://example.com/video.mp4\" poster=\"https://example.com/poster.jpg\" aspectRatio={16 / 9} /&gt;);} Enter fullscreen mode Exit fullscreen mode Compound API for custom control bars The default control bar is just a sensible composition of compound sub-components. Pass controls={false} and bring your own children to reorder, remove, or add controls — without losing Mantine theming or accessibility. &lt;Video src=\"...\" aspectRatio={16 / 9} controls={false}&gt;&lt;Video.Controls&gt; &lt;Video.PlayButton /&gt; &lt;Video.SkipButton seconds={-10} /&gt; &lt;Video.SkipButton seconds={10} /&gt; &lt;Video.Timeline /&gt; &lt;Video.TimeDisplay format=\"current/-remaining\" /&gt; &lt;Video.MuteButton /&gt; &lt;Video.CaptionsButton /&gt; &lt;Video.PiPButton /&gt; &lt;Video.FullscreenButton /&gt;&lt;/Video.Controls&gt;&lt;/Video&gt; Enter fullscreen mode Exit fullscreen mode Nine compound sub-components available: Video.Controls, Video.PlayButton, Video.SkipButton, Video.Timeline, Video.TimeDisplay, Video.MuteButton, Video.CaptionsButton, Video.PiPButton, Video.FullscreenButton. Headless useVideo hook When the compound API isn't flexible enough, skip the &lt;Video /&gt; component entirely and use the useVideo hook with a plain &lt;video&gt; element. You get full state plus a complete set of actions. import { ActionIcon, Slider } from '@mantine/core';import { IconPlayerPauseFilled, IconPlayerPlayFilled } from '@tabler/icons-react';import { useVideo } from '@gfazioli/mantine-video'; function CustomPlayer() {const video = useVideo(); return ( &lt;div&gt; &lt;video ref={video.videoRef} src=\"...\" /&gt; &lt;ActionIcon onClick={video.toggle}&gt; {video.playing ? &lt;IconPlayerPauseFilled /&gt; : &lt;IconPlayerPlayFilled /&gt;} &lt;/ActionIcon&gt; &lt;Slider value={video.currentTime} max={video.duration} onChange={video.seek} /&gt; &lt;/div&gt;);} Enter fullscreen mode Exit fullscreen mode The hook exposes 16 state values (playing, paused, ended, currentTime, duration, buffered, volume, muted, playbackRate, fullscreen, pip, isLoading, error, canPlay, canFullscreen, canPiP) and 16 actions (play, pause, toggle, seek, seekBy, setVolume, mute, unmute, toggleMute, setPlaybackRate, requestFullscreen, exitFullscreen, toggleFullscreen, requestPiP, exitPiP, togglePiP) plus the two refs (videoRef, containerRef). Live timeline scrubbing Drag the timeline thumb — the underlying &lt;video&gt; seeks in real time and you see the frame update under your finger, YouTube-style. The player pauses automatically during the drag and resumes on release if it was playing. Throttled with requestAnimationFrame so it stays smooth even on heavier videos. Opt out with &lt;Video.Timeline liveScrub={false} /&gt; to fall back to commit-on-release. Four built-in variants &lt;Video src=\"...\" variant=\"overlay\" /&gt; {/* default — YouTube-style overlay */}&lt;Video src=\"...\" variant=\"minimal\" /&gt; {/* controls below the video, page flow */}&lt;Video src=\"...\" variant=\"floating\" /&gt; {/* controls in a glass-morphism card */}&lt;Video src=\"...\" variant=\"bordered\" /&gt; {/* bordered frame for cards/lists */} Enter fullscreen mode Exit fullscreen mode Each variant changes only the position and styling of the controls — the API and sub-components stay identical. Background video — one prop turns the player into a hero This is where the layered API really pays off. Set asBackground and the same player becomes an absolute-positioned, cover-cropped element ready to drop inside any hero section. &lt;Box pos=\"relative\" h=\"100vh\"&gt;&lt;Video src=\"...\" asBackground autoPlay muted loop /&gt;&lt;Title&gt;Welcome to my product&lt;/Title&gt;{/* Your hero content overlaid on top */}&lt;/Box&gt; Enter fullscreen mode Exit fullscreen mode When asBackground is on, the component: Positions itself absolutely (position: absolute; inset: 0) inside its parent Applies object-fit: cover on the underlying &lt;video&gt; Disables controls, clickToToggle, shortcuts and autoHideControls as defaults (you can still re-enable any of them explicitly) Renders a discreet floating mute toggle in the bottom-right corner, controllable via the backgroundMuteButton prop For finer control, the standalone fit prop accepts 'cover' | 'contain' | 'fill' | 'none' | 'scale-down' and maps directly to CSS object-fit. Picture-in-Picture PiP is wired in by default. The browser's native always-on-top window can be launched via: The &lt;Video.PiPButton /&gt; — auto-hides on browsers without the standard API (notably Firefox) The P keyboard shortcut Programmatically via useVideo().togglePiP() Plus two lifecycle callbacks to react to the user popping the video out: &lt;Videosrc=\"...\"onEnterPictureInPicture={() =&gt; console.log('PiP open')}onLeavePictureInPicture={() =&gt; console.log('PiP closed')}/&gt; Enter fullscreen mode Exit fullscreen mode Keyboard shortcuts When shortcuts is enabled (default) and the player has focus: Space / K — toggle play / pause J / L — seek -10s / +10s ← / → — seek -5s / +5s ↑ / ↓ — volume up / down M — mute toggle F — fullscreen toggle P — Picture-in-Picture toggle 🚀 Getting Started npm install @gfazioli/mantine-video # or yarn add @gfazioli/mantine-video Enter fullscreen mode Exit fullscreen mode Import the styles at the root of your application: import '@gfazioli/mantine-video/styles.css'; Enter fullscreen mode Exit fullscreen mode A layered version is also available under @layer mantine-video for fine-grained cascade control: import '@gfazioli/mantine-video/styles.layer.css'; Enter fullscreen mode Exit fullscreen mode Then drop it anywhere: import { Video } from '@gfazioli/mantine-video'; export function Demo() {return ( &lt;Video src=\"https://example.com/video.mp4\" poster=\"https://example.com/poster.jpg\" aspectRatio={16 / 9} /&gt;);} Enter fullscreen mode Exit fullscreen mode Props &amp; API The &lt;Video /&gt; component accepts a rich set of props grouped by responsibility: Source &amp; playback src: string — video source URL poster: string — image displayed before playback starts autoPlay, muted, loop, playsInline, preload — forwarded to the underlying &lt;video&gt; playing / currentTime / volume / playbackRate — controlled props with matching onPlayChange / onCurrentTimeChange / onVolumeChange / onPlaybackRateChange callbacks Look &amp; feel variant: 'overlay' | 'minimal' | 'floating' | 'bordered' color: MantineColor radius: MantineRadius size: MantineSize aspectRatio: number — e.g. 16 / 9 Interactivity controls: boolean — render the default control bar (default true) clickToToggle: boolean — click anywhere on the video to play / pause doubleClickToFullscreen: boolean shortcuts: boolean — enable keyboard shortcuts when focused autoHideControls: number — ms of inactivity before controls fade out (0 disables) Background mode asBackground: boolean — preset for hero / section backgrounds backgroundMuteButton: boolean — floating mute toggle when asBackground is on (default true) fit: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down' — CSS object-fit on the &lt;video&gt; Lifecycle callbacks onEnded() — playback reached the end of the media onError(error: MediaError | null) onEnterPictureInPicture() / onLeavePictureInPicture() onFullscreenChange(fullscreen: boolean) 🎨 Styling &lt;Video /&gt; supports the full Mantine Styles API. Every sub-part is targetable through classNames, styles, vars and unstyled props. Selectors: root, video, controls, controlBar, playButton, timeline, timelineBuffered, timeDisplay, muteButton, fullscreenButton, pipButton, captionsButton, skipButton, iconButton, backgroundMuteButton. CSS variables: --video-color, --video-radius, --video-bg, --video-controls-bg, --video-controls-height, --video-controls-text-color, --video-timeline-color, --video-timeline-thumb-color, --video-object-fit. &lt;Videosrc=\"...\"classNames={{ playButton: 'my-play-button' }}styles={{ controls: { background: 'transparent' } }}vars={() =&gt; ({ root: { '--video-color': 'tomato' } as Record&lt;string, string&gt;,})}/&gt; Enter fullscreen mode Exit fullscreen mode Live Demo &amp; Documentation The docs site at gfazioli.github.io/mantine-video ships with ten interactive demos covering every API surface — an interactive configurator, the basic player, custom controls, each of the four variants, headless usage via useVideo, Picture-in-Picture lifecycle, and the Styles API playground. There are also two full-bleed standalone pages that show the asBackground mode in real landing-page scenarios: /fullscreen — a single immersive hero with title + CTA over a looping video /homepage — a multi-section landing page that alternates three different background videos with text content and a features grid Links 📦 npm: @gfazioli/mantine-video 📖 Documentation &amp; demos 🔗 GitHub repository 🧩 Mantine Extensions Hub","contentHash":"sha256:06ccd0cc188b331295156e0e1cd2148ac2d25b6fb336cdb391b6e70048a65d1a","authorName":"Giovambattista Fazioli","authorUrl":"https://api.zyvop.com/author/giovambattista","authorSameAs":["https://gfazioli.github.io","https://github.com/gfazioli","https://x.com/gfazioli","https://www.linkedin.com/in/giovambattistafazioli/"],"category":null,"tags":["react","reactjsdevelopment","TypeScript","webcomponents"],"audience":"Readers researching react","tone":"Professional, senior full-stack engineer, lead developer perspective","readingTimeMinutes":7,"wordCount":1465,"faqs":null,"primaryTopic":"react","publishedAt":"2026-09-04T09:27:24.060Z","updatedAt":"2026-09-04T09:27:24.060Z","canonicalUrl":"https://dev.to/undolog/the-player-the-pieces-and-the-hook-59i6"}