Video lightbox
Also called video modal, video popup, or hero video dialog
A video thumbnail with a play button that opens the video in a large overlay, growing out of the thumbnail, and closes on Esc or a click outside.
import { useEffect, useRef, useState } from 'react';
import { AnimatePresence, motion, useReducedMotion } from 'motion/react';
type Props = { poster: string; src: string; title: string };
export function VideoLightbox({ poster, src, title }: Props) {
const [open, setOpen] = useState(false);
const trigger = useRef<HTMLButtonElement>(null);
// A shared layoutId makes the player grow out of the thumbnail.
const layoutId = useReducedMotion() ? undefined : `video-${src}`;
useEffect(() => {
if (!open) return;
const button = trigger.current;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') setOpen(false);
};
document.addEventListener('keydown', onKeyDown);
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', onKeyDown);
document.body.style.overflow = '';
button?.focus(); // hand focus back to the play button
};
}, [open]);
return (
<>
<button
ref={trigger}
type='button'
onClick={() => setOpen(true)}
aria-label={`Play ${title}`}
className='group relative aspect-video w-full max-w-2xl'
>
{!open && (
<motion.img
layoutId={layoutId}
src={poster}
alt=''
className='size-full rounded-2xl object-cover'
/>
)}
<span className='absolute inset-0 m-auto grid size-18 place-items-center rounded-full bg-white/90 text-2xl text-black shadow-lg transition-transform group-hover:scale-110'>
▶
</span>
</button>
<AnimatePresence>
{open && (
<motion.div
role='dialog'
aria-modal='true'
aria-label={title}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setOpen(false)}
className='fixed inset-0 z-50 grid place-items-center bg-black/60 p-4'
>
<motion.div
layoutId={layoutId}
onClick={(event) => event.stopPropagation()}
className='relative aspect-video w-[90vw] max-w-[960px] overflow-hidden rounded-2xl bg-black'
>
{/* The video only loads once the dialog opens. */}
<video src={src} controls autoPlay className='size-full' />
<button
autoFocus
onClick={() => setOpen(false)}
aria-label='Close video'
className='absolute top-3 right-3 grid size-9 place-items-center rounded-full bg-black/60 text-white'
>
✕
</button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</>
);
}In the hero, show a 16:9 product tour thumbnail with 16px corners and a 72px white play button in the center that scales to 1.1× on hover. Clicking it opens a dialog: the page dims to 60% black over 200ms and the thumbnail grows into a centered player 90vw wide (max 960px) with a shared-element animation, then a <video> starts playing. Close on Esc, backdrop click, or an X button, shrinking back into the thumbnail and pausing the video. Return focus to the play button, and fade instead of growing under prefers-reduced-motion.
Paste into Layerwise or any AI coding tool.
What is Video lightbox?
A video lightbox keeps a heavy video off the page until someone asks for it. The page shows a poster image with a big play button. Clicking it opens a dialog: the backdrop dims, the player grows from the thumbnail’s position to fill most of the screen, and the video starts. Pressing Esc, clicking the backdrop, or pressing the close button shrinks it back into the thumbnail.
It suits product tours, demo videos, and talks on landing pages, where most visitors only want to read but some want the full walkthrough. Loading the player, a <video> element or an embed from a video host, only when the dialog opens keeps the page fast.
Build it as a real modal dialog: move focus to the close button on open and back to the play button on close, keep the page behind from scrolling, and give the play button a name like “Play product tour (2:14)”. Pause or unload the video when it closes. Under reduced motion, fade the dialog in instead of growing it.
Often confused with
- Modal
- A modal is any dialog that blocks the page until it closes. A video lightbox is a modal built around one video and opened from its thumbnail.
Related terms
All termsModal
A window that opens over the page, dims everything behind it, and blocks the rest of the interface until you finish or dismiss it.
Shared element transition
A transition where an element from one view, like a thumbnail, travels and grows into its place in the next view, linking the two screens.
Hero section
The large first section at the top of a landing page, with a headline, a short subhead, a call to action, and a visual.
Lazy loading
A technique that waits to load images, videos, or code until they are about to scroll into view, so the first screen loads faster.
Build it with Layerwise
Describe your app, watch it run in a live preview, and publish it with a database, storage, and sign-in built in.