Skip to content

Hero Video Dialog

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.

Motionv0.1.0experimentalWCAG 2.2 AAView spec

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-dialog

Copies 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.json

Works, 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.

PropTypeDefault
videoSrcrequired

URL of the video file you supply. It loads only after the dialog opens.

stringnone
titlerequired

Name of the video. It names the thumbnail button and the dialog.

stringnone
poster

The thumbnail: an image URL string, or any element such as an img or a gradient.

nodenone
tracks

track elements for captions and descriptions, placed inside the video.

nodenone
isStill

Turns off the hover zoom of the thumbnail.

booleanfalse

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