· 1 min read

Autoplay video heroes that everyone can use

A full-screen background video looks great, but moving content needs a way to stop it. Here is how I build video heroes that pass WCAG and still feel cinematic.

  • Accessibility
  • React
  • Video

Biotech and pharma clients love a full-screen video behind the headline. It sets a mood in a few seconds. On sites like Mariana Oncology I built exactly that, and the part that takes the most care is not the video itself but the controls around it.

Why a pause button is required

WCAG 2.2.2 (Pause, Stop, Hide) says that anything that moves automatically for more than five seconds, and plays alongside other content, needs a way for the visitor to pause it. Motion can make text hard to read, and for people with vestibular disorders it can cause real discomfort. A looping background video falls squarely under this rule.

The checklist I follow

  • Muted, inline and looping: muted and playsInline let browsers autoplay it, including on phones.
  • A poster image, so something meaningful shows before the video loads or if it fails.
  • A visible pause/play button with a clear accessible name, reachable by keyboard.
  • Respect prefers-reduced-motion: if the visitor asked their system for less motion, start paused.
  • Keep the headline readable on every frame with an overlay, not just on the first one.
  • Mark the video as decorative (aria-hidden) when it carries no information of its own.

A minimal React version

function HeroVideo({ src, poster }: { src: string; poster: string }) {
  const ref = useRef<HTMLVideoElement>(null);
  const [playing, setPlaying] = useState(true);

  useEffect(() => {
    // Start paused for visitors who prefer reduced motion.
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      ref.current?.pause();
      setPlaying(false);
    }
  }, []);

  const toggle = () => {
    const video = ref.current;
    if (!video) return;
    if (video.paused) void video.play();
    else video.pause();
    setPlaying(!video.paused);
  };

  return (
    <div className="hero-media">
      <video ref={ref} src={src} poster={poster} autoPlay muted loop playsInline aria-hidden="true" />
      <button type="button" onClick={toggle} aria-label={playing ? "Pause background video" : "Play background video"}>
        {playing ? "Pause" : "Play"}
      </button>
    </div>
  );
}

The button's label changes with its state, so screen reader users always hear what pressing it will do. Put it somewhere predictable, such as the bottom corner of the hero, and make sure its focus outline is visible against the video.

None of this changes how the hero looks for most visitors. It just means the people who need to stop the motion can.