Kit Cross

Dark mode videos with prefers-color-scheme

Dark mode has wide browser support and media queries allow for switching images by user color scheme. The same is also possible with video elements.

To my delight, dark mode is slowly spreading across the web. More and more sites now respect your settings and display in a dark version. Including this one.

A typical site has text, images and video. Of the three, adapting text to a user’s theme is the easiest. You can use a media query for prefers-color-scheme and style the page with CSS variables for light and dark colors.

For images, the picture element allows media queries in the source list:

<picture>
  <source srcset="dark.jpg" media="(prefers-color-scheme: dark)">
  <img src="light.jpg">
</picture>

This works well and has wide browser support. How about video? You might try the following:

<video>
  <source src="dark.mp4" type="video/mp4" media="(prefers-color-scheme: dark)">
  <source src="light.mp4" type="video/mp4">
</video>

This does not work.

Safari will load the right source. But it won’t change the video source when the theme changes. Chrome and Firefox simply load the first source and ignore the media query.

We want to accomplish the following:

  • load the correct source depending on the user’s color scheme
  • only load the right video source and avoid wasting bandwidth by loading both
  • change video sources when the color scheme changes

Setting the source with Javascript

Until we get media query support in the video element we’ll need Javascript to do this. An empty video tag is the container for our source elements.

<video class="dark-mode-video" muted loop playsinline>
</video>

The setVideoSource function attaches the light or dark source to the video container. It’s important to use video.load() to force the browser to refresh the source list.

We also remove all the existing source elements. This prevents multiple source elements being added if the user toggles their theme while on the page.

function setVideoSource() {
  const src = document.createElement('source');
  const video = document.querySelector('.dark-mode-video');

  // Remove old source elements
  while (video.firstChild) {
    video.removeChild(video.lastChild);
  }

  src.type = 'video/mp4';

  if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
    src.src = 'dark.mp4';
  } else {
    src.src = 'light.mp4';
  }

  video.appendChild(src);
  video.load();
  video.play();
}

The final part of the puzzle is detecting changes in the color scheme. We can use window.matchMedia to add an event listener when the theme changes.

window.matchMedia('(prefers-color-scheme: dark)').addListener(e => {
  setVideoSource();
});

It seems that media queries were once supported in videos, before being removed. In documentation for the source element, MDN states:

This should be used only in a picture element

This should be reconsidered. Videos are increasingly used as design elements and background layers. We shouldn’t need to use Javascript to create our own media query logic.

web video javascript