A poster thumbnail with a play button that opens a dialog with your video. No video is bundled and none is fetched until the dialog opens. Focus moves into the dialog and returns to the thumbnail on close; Escape closes.
Optional, and kept apart.
Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.
import { HeroVideoDialog } from "@rdloom/react";
// A public sample video (CC0) and a free Unsplash photo as the thumbnail. Use your own files. The video is not
// requested until the dialog opens.
export default function HeroVideoDialogBasicExample() {
return (
<div className="flex w-full justify-center">
<div className="w-full max-w-3xl">
<HeroVideoDialog
title="Product tour"
videoSrc="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
poster={<img src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=1200&q=80" alt="" className="size-full object-cover" />}
/>
</div>
</div>
);
}Installation
npx rdloom add hero-video-dialogCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.
It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/hero-video-dialog.jsonWorks, but without upgrade tracking, and you import the motion CSS yourself.
Usage
import { HeroVideoDialog } from "@rdloom/react";
<div className="flex w-full justify-center">
<div className="w-full max-w-3xl">
<HeroVideoDialog
title="Product tour"
videoSrc="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
poster={<img src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=1200&q=80" alt="" className="size-full object-cover" />}
/>
</div>
</div>With captions
import { HeroVideoDialog } from "@rdloom/react";
// Pass track elements for captions: a video without captions fails WCAG 1.2.2. Here the captions are a small inline
// file; use a .vtt file of your own. The video is a public CC0 sample and the thumbnail a free Unsplash photo.
const captions = `data:text/vtt;charset=utf-8,${encodeURIComponent(
"WEBVTT\n\n00:00.000 --> 00:04.000\nA flower opens in the morning light.\n\n00:04.000 --> 00:08.000\nThe petals unfold one by one.",
)}`;
export default function HeroVideoDialogWithCaptionsExample() {
return (
<div className="flex w-full justify-center">
<div className="w-full max-w-3xl">
<HeroVideoDialog
title="A flower opening"
videoSrc="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
poster={<img src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=1200&q=80" alt="" className="size-full object-cover" />}
tracks={<track kind="captions" src={captions} srcLang="en" label="English" default />}
/>
</div>
</div>
);
}Still
import { HeroVideoDialog } from "@rdloom/react";
// isStill removes the hover zoom of the thumbnail. The dialog behaves the same.
export default function HeroVideoDialogStillExample() {
return (
<div className="flex w-full justify-center">
<div className="w-full max-w-3xl">
<HeroVideoDialog
isStill
title="Release notes walkthrough"
videoSrc="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
poster={<img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&w=1200&q=80" alt="" className="size-full object-cover" />}
/>
</div>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
videoSrcrequiredURL of the video file you supply. It loads only after the dialog opens. | string | none |
titlerequiredName of the video. It names the thumbnail button and the dialog. | string | none |
posterThe thumbnail: an image URL string, or any element such as an img or a gradient. | node | none |
trackstrack elements for captions and descriptions, placed inside the video. | node | none |
isStillTurns off the hover zoom of the thumbnail. | boolean | false |
Accessibility
Role button, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Enter or Space: opens the video dialog
- Escape: closes it and returns focus to the thumbnail
- Tab: moves between the video controls and the Close button
Screen readers announce
- Button: Play video: title. Dialog: title, video controls, Close button
What your code must do
- The thumbnail is a real button named Play video: followed by the title, and opens a modal dialog with the same title
- The poster image is decoration (alt empty): the button name carries the meaning
- The dialog traps focus, closes with Escape or the Close button, and returns focus to the thumbnail
- The video has native controls and does not start by itself loudly: it begins when the visitor opens it, and they can pause it
- Supply captions through tracks; a video without captions fails WCAG 1.2.2
- Nothing is loaded from videoSrc until the dialog opens; the hover zoom is off for reduced motion and isStill
Guidelines
Use it when
- A product demo on a landing page
- A tutorial the visitor chooses to watch
Avoid it when
- Video that must be visible without an action
- Background loops
Don't
- Shipping a video without captions
- Autoplaying with sound on page load
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-surface-raised--rd-color-focus-ring--rd-radius-overlay