Video is used for media layout.

Figma:

Responsive:

Adaptive:

Props

Component props
Name
Type
Default
aspectRatio
Required
number
-

Proportional relationship between width and height of the video, calculated as width / height.

onPlay
Required
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered when pause is changed from "true" to "false" or autoplay. See the MDN Web Docs: play event and the autoplay and error detection variant to learn more.

onPlayError
Required
(arg1: { error: Error }) => void
-

Callback triggered when a play() method's Promise is rejected. See the autoplay and error detection variant to learn more.

src
Required
string
| ReadonlyArray<{
    type: "video/m3u8" | "video/mp4" | "video/ogg";
    src: string;
  }>
-

The URL of the video file to play. This can also be supplied as a list of video types to respective video source urls in fallback order for support on various browsers. See multiple sources example for more details.

autoplay
boolean
-
backgroundColor
"black" | "transparent"
"black"

Background color used to fill the video's placeholder.

captions
string
-

The URL of the captions track for the video (.vtt file). See the accessibility section to learn more.

children
React.Node
-

This children prop is not same as children inside the native html video element. Instead, it serves to add overlays on top of the html video element, while still being under the video controls. See children example for more details.

controls
boolean
-

Show the video control interface. See the video controls variant to learn more.

crossOrigin
"anonymous" | "use-credentials"
-

Designate CORS behavior for the video element. When not passed in, CORS checks are disabled.

dataTestId
string
-

Available for testing purposes, if needed. Consider better queries before using this prop.

disableRemotePlayback
boolean
false

Disable remote playback. See MDN Web Docs: disableRemotePlayback for more info.

loop
boolean
-

Indicates if the video will start playing over again when finished.

objectFit
"fill" | "contain" | "cover" | "none" | "scale-down"
-

Sets how the content of the replaced video element should be resized to fit its container.

onControlsPause
(arg1: { event: React.SyntheticEvent<HTMLDivElement> }) => void
-

Callback triggered when playback is paused via the video control interface.

onControlsPlay
(arg1: { event: React.SyntheticEvent<HTMLDivElement> }) => void
-

Callback triggered when playback is played via the video control interface.

onDurationChange
(arg1: {
  event: React.SyntheticEvent<HTMLVideoElement>;
  duration: number;
}) => void
-

Callback triggered when the metadata has loaded or changed, indicating a change in duration. See the MDN Web Docs: durationchange event.

onEnded
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered when playback of the video completes. See the MDN Web Docs: ended event.

onError
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered when an error occurs. See the MDN Web Docs: onerror.

onFullscreenChange
(arg1: { event: Event; fullscreen: boolean }) => void
-

Callback triggered when the video full-screen status changes. See the video controls variant to learn more.

onLoadedChange
(arg1: {
  event: React.SyntheticEvent<HTMLVideoElement>;
  loaded: number;
}) => void
-

Callback triggered when progress happens on downloading the media. See the MDN Web Docs: progress event.

onLoadStart
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered when the media has started to load.

onPause
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered when playback is paused. See the MDN Web Docs: pause event.

onPlayheadDown
(arg1: {
  event: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>;
}) => void
-

Callback triggered when mousedown event occurs on the playhead via the video control interface. See the video controls variant to learn more.

onPlayheadUp
(arg1: {
  event: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>;
}) => void
-

Callback triggered when mouseup event occurs on the playhead via the video control interface. See the video controls variant to learn more.

onPlaying
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered after playback is first started, and whenever it is restarted. See the MDN Web Docs: playing event.

onReady
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered when enough data is available that the media can be played. See the MDN Web Docs: canplay event.

onSeek
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered when a seek operation completes from the playhead. See the MDN Web Docs: seeked event.

onSeeking
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered when a seek operation begins. See the MDN Web Docs: seeking event.

onStalled
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered when trying to fetch data but the data is unexpectedly not forthcoming. See the MDN Web Docs: stalled event.

onTimeChange
(arg1: { event: React.SyntheticEvent<HTMLVideoElement>; time: number }) => void
-

Callback triggered when the time indicated by the element's currentTime attribute has changed. See the MDN Web Docs: timeupdate event.

onVolumeChange
(arg1: { event: React.SyntheticEvent<HTMLDivElement>; volume: number }) => void
-

Callback triggered when the audio volume changes via the video control interface. See the video updates variant to learn more.

onWaiting
(arg1: { event: React.SyntheticEvent<HTMLVideoElement> }) => void
-

Callback triggered when playback has stopped because of a temporary lack of data. See the MDN Web Docs: waiting event.

playbackRate
number
1

Specifies the speed at which the video plays: 1 for normal. See the video updates variant to learn more.

playing
boolean
false

Specifies whether the video should play or not. See autoplay and error detection variant to learn more.

playsInline
boolean
-

Serves as a hint to the user agent that the video should to be displayed "inline" in the document by default, constrained to the element's playback area, instead of being displayed fullscreen or in an independent resizable window. This attribute is mainly relevant to iOS Safari browsers. See the MDN Web Docs: playsinline

poster
string
-

The image to show while the video is loading. See the video controls variant to learn more.

preload
"auto" | "metadata" | "none"
"auto"

Specifies how, if at all, the video should be pre-loaded when the page loads. See the MDN Web Docs: preload

ref
React.Ref<typeof Video>
-

Ref on the Gestalt Video component.

startTime
number
0

Set the current play time in seconds the video will start from. See MDN Web Docs: HTMLMediaElement.currentTime

volume
number
0

Specifies the volume of the video audio: 0 for muted, 1 for max. See the video controls variant to learn more.

Accessibility

Captions

Captions are intended for deaf and hard-of-hearing audiences. Captions are usually in the same language as the audio. Please, read the differences between captions and subtitles.

Read more about adding captions to video.

The following example uses an excerpt from the Sintel open movie, created by the Blender Foundation.

import { useState } from 'react';
import { Box, Video } from 'gestalt';

export default function Example() {
  const [playing, setPlaying] = useState(false);
  const [volume, setVolume] = useState(1);

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Box width={1000}>
        <Video
          aspectRatio={1024 / 435}
          captions="https://iandevlin.github.io/mdn/video-player-with-captions/subtitles/vtt/sintel-en.vtt"
          controls
          loop
          onControlsPause={() => setPlaying(false)}
          onControlsPlay={() => setPlaying(true)}
          onEnded={() => setPlaying(false)}
          onPlay={() => setPlaying(true)}
          onPlayError={({ error }) => error && setPlaying(false)}
          onVolumeChange={(e) => setVolume(e.volume)}
          playing={playing}
          src="https://iandevlin.github.io/mdn/video-player-with-captions/video/sintel-short.mp4"
          volume={volume}
        />
      </Box>
    </Box>
  );
}

Localization

Be sure to localize all text strings. Note that localization can lengthen text by 20 to 30 percent.

Video depends on DefaultLabelProvider for internal text strings. Localize the texts via DefaultLabelProvider. Learn more
import { useState } from 'react';
import { Box, DefaultLabelProvider, Video } from 'gestalt';

export default function Example() {
  const [playing, setPlaying] = useState(false);
  const [volume, setVolume] = useState(1);

  return (
    <DefaultLabelProvider
      labels={{
        Video: {
          accessibilityMaximizeLabel: 'Maximieren',
          accessibilityMinimizeLabel: 'Minimieren',
          accessibilityMuteLabel: 'Stummschalten',
          accessibilityPauseLabel: 'Pause',
          accessibilityPlayLabel: 'Spielen',
          accessibilityProgressLabel: 'Video Fortschritt',
          accessibilityUnmuteLabel: 'Stummschaltung aufheben',
          accessibilityHideCaptionsLabel: 'Bildunterschriften ausblenden',
          accessibilityShowCaptionsLabel: 'Bildunterschriften anzeigen',
        },
      }}
    >
      <Box
        alignItems="center"
        display="flex"
        height="100%"
        justifyContent="center"
        padding={8}
      >
        <Box width={300}>
          <Video
            aspectRatio={540 / 960}
            controls
            onControlsPause={() => setPlaying(false)}
            onControlsPlay={() => setPlaying(true)}
            onEnded={() => setPlaying(false)}
            onPlay={() => setPlaying(true)}
            onPlayError={({ error }) => error && setPlaying(false)}
            onVolumeChange={(e) => setVolume(e.volume)}
            playing={playing}
            src="https://v.pinimg.com/videos/mc/expMp4/c8/37/71/c83771d856bc1ee12e2d2f81083df9d4_t1.mp4"
            volume={volume}
          />
        </Box>
      </Box>
    </DefaultLabelProvider>
  );
}

Variants

Autoplay and error detection

Autoplay or automatically starting the playback of the video requires the autoplay prop. While autoplay of media serves a useful purpose, it should be used carefully and only when needed. In order to give users control over this, browsers often provide various forms of autoplay blocking.

Autoplay blocking is not applied to video elements when the source media doesn't have an audio track or is muted.

Gestalt Video provides a comprehensive API to handle gracefully autoplay blocking and prevent UI and/or client errors. The example below shows how to correctly handle autoplay error detection.

If autoplay is set, use onPlay to detect when the video starts playing and display the pause control accordingly. In case autoplay gets blocked, onPlay would never get triggered and controls will still display the play control.

onPlayError is another error-handling callback. In this case, onPlayError detects if the HTMLMediaElement.play() method fails. HTMLMediaElement.play() returns a Promise and onPlayError catches the error if the Promise is rejected. Display the pause control if any error is detected.

If autoplay is set, don't set the initial playing state to true as both will attempt to autoplay the video. We recommend setting autoplay, using onPlay to detect when the video is played and setting playing to true. If the initial playing state is set to true, don't set autoplay. If both autoplay and the initial playing state are set to true, autoplay has preference.

For more information about autoplay, check the MDN Web Docs: video, MDN Web Docs: HTMLMediaElement.autoplay, and the MDN Web Docs: Autoplay guide for media and Web Audio APIs.

import { useState } from 'react';
import { Box, Video } from 'gestalt';

export default function Example() {
  const [playing, setPlaying] = useState(false);
  const [volume, setVolume] = useState(1);

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Box width={300}>
        <Video
          aspectRatio={540 / 960}
          autoplay
          controls
          loop
          onControlsPause={() => setPlaying(false)}
          onControlsPlay={() => setPlaying(true)}
          onEnded={() => setPlaying(false)}
          onPlay={() => setPlaying(true)}
          onPlayError={({ error }) => error && setPlaying(false)}
          onVolumeChange={(e) => setVolume(e.volume)}
          playing={playing}
          src="https://v.pinimg.com/videos/mc/expMp4/c8/37/71/c83771d856bc1ee12e2d2f81083df9d4_t1.mp4"
          volume={volume}
        />
      </Box>
    </Box>
  );
}

Video controls

Video components can show a control bar to users in order to allow them access to certain features such as play/pause, timestamps, mute, and fullscreen. Pass in the controls prop to make them appear. The Video controls are custom; they aren't the native video controls.

import { useState } from 'react';
import { Box, Flex, IconButton, Label, Switch, Text, Video } from 'gestalt';

export default function Example() {
  const [showControls, setShowControls] = useState(false);
  const [playing, setPlaying] = useState(false);
  const [volume, setVolume] = useState(1);

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Flex alignItems="center" direction="column" gap={{ column: 2, row: 0 }}>
        <Flex alignItems="center" gap={{ row: 2, column: 0 }}>
          <Box paddingX={2}>
            <Label htmlFor="video">
              <Text>Show built-in Video controls</Text>
            </Label>
          </Box>
          <Switch
            id="video"
            onChange={() => setShowControls((value) => !value)}
            switched={showControls}
          />
        </Flex>
        <Box width={300}>
          <Video
            aspectRatio={540 / 960}
            controls={showControls}
            onControlsPause={() => setPlaying(false)}
            onControlsPlay={() => setPlaying(true)}
            onEnded={() => setPlaying(false)}
            onPlay={() => setPlaying(true)}
            onPlayError={({ error }) => error && setPlaying(false)}
            onVolumeChange={(e) => setVolume(e.volume)}
            playing={playing}
            poster="https://i.pinimg.com/videos/thumbnails/originals/c8/37/71/c83771d856bc1ee12e2d2f81083df9d4.0000000.jpg"
            src="https://v.pinimg.com/videos/mc/expMp4/c8/37/71/c83771d856bc1ee12e2d2f81083df9d4_t1.mp4"
            volume={volume}
          >
            {!showControls ? (
              <Box
                alignItems="center"
                dangerouslySetInlineStyle={{
                  __style: { backgroundColor: 'rgb(0 0 0 / 0.3)' },
                }}
                display="flex"
                height="100%"
                justifyContent="center"
                width="100%"
              >
                <IconButton
                  accessibilityLabel="Play video"
                  bgColor="white"
                  icon="play"
                  size="lg"
                />
              </Box>
            ) : null}
          </Video>
        </Box>
      </Flex>
    </Box>
  );
}

With children

Video component can show components in the children prop on top of the html video element, while under the controls.
The children of Video aren't same as the children of the html video element; they're "outside" the html video element.

import { useState } from 'react';
import { Box, IconButton, Video } from 'gestalt';

export default function Example() {
  const [playing, setPlaying] = useState(false);
  const [volume, setVolume] = useState(1);

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Box width={300}>
        <Video
          aspectRatio={540 / 960}
          controls
          onControlsPause={() => setPlaying(false)}
          onControlsPlay={() => setPlaying(true)}
          onEnded={() => setPlaying(false)}
          onPlay={() => setPlaying(true)}
          onPlayError={({ error }) => error && setPlaying(false)}
          onVolumeChange={(e) => setVolume(e.volume)}
          playing={playing}
          src="https://v.pinimg.com/videos/mc/expMp4/c8/37/71/c83771d856bc1ee12e2d2f81083df9d4_t1.mp4"
          volume={volume}
        >
          <Box
            alignItems="center"
            dangerouslySetInlineStyle={{
              __style: { backgroundColor: 'rgb(0 0 0 / 0.3)' },
            }}
            display="flex"
            height="100%"
            justifyContent="center"
            width="100%"
          >
            <IconButton
              accessibilityLabel="Delete video"
              bgColor="white"
              icon="trash-can"
              size="lg"
            />
          </Box>
        </Video>
      </Box>
    </Box>
  );
}

Video updates

Video is robust enough to handle any updates, such as changing the source, volume, or speed.

import { useState } from 'react';
import { Box, Button, Flex, Label, Text, TextField, Video } from 'gestalt';

export default function Example() {
  const [input, setInput] = useState(
    'https://v.pinimg.com/videos/mc/expMp4/ce/b4/cc/ceb4cc8c4889a86432a65884c147021f_t1.mp4'
  );
  const [playbackRate, setPlaybackRate] = useState(1);
  const [playing, setPlaying] = useState(false);
  const [src, setSrc] = useState(
    'https://v.pinimg.com/videos/mc/expMp4/c8/37/71/c83771d856bc1ee12e2d2f81083df9d4_t1.mp4'
  );
  const [volume, setVolume] = useState(1);

  return (
    <Box display="flex" height="100%" justifyContent="center" padding={8}>
      <Flex direction="column" gap={{ column: 2, row: 0 }} width="100%">
        <Label htmlFor="video-source">
          <Text>Video source URL</Text>
        </Label>
        <Flex alignItems="center" gap={{ row: 2, column: 0 }} width="100%">
          <Flex.Item flex="grow">
            <TextField
              id="video-source"
              onChange={({ value }) => setInput(value)}
              value={input}
            />
          </Flex.Item>
          <Button color="red" onClick={() => setSrc(input)} text="Submit" />
        </Flex>
        <Flex gap={{ column: 0, row: 2 }}>
          <Button
            onClick={() => setVolume(volume === 0 ? 1 : 0)}
            text={volume === 0 ? 'Unmute' : 'Mute'}
          />
          <Button
            onClick={() =>
              setPlaybackRate((value) => (value >= 1 ? value / 2 : 0.5))
            }
            text="Playback x0.5"
          />
          <Button
            onClick={() =>
              setPlaybackRate((value) => (value < 8 ? value * 2 : 16))
            }
            text="Playback x2"
          />
        </Flex>
        <Box width={300}>
          <Video
            aspectRatio={540 / 960}
            controls
            loop
            onControlsPause={() => setPlaying(false)}
            onControlsPlay={() => setPlaying(true)}
            onPlay={() => setPlaying(true)}
            onPlayError={({ error }) => error && setPlaying(false)}
            onVolumeChange={(e) => setVolume(e.volume)}
            playbackRate={playbackRate}
            playing={playing}
            src={src}
            volume={volume}
          />
        </Box>
      </Flex>
    </Box>
  );
}

Multiple video sources

Not all browsers support the same video encoding types. If you have multiple video file sources, you can pass them as a list to Video in the order you want the HTML video tag to use as fallbacks.

import { useState } from 'react';
import { Box, Video } from 'gestalt';

export default function Example() {
  const [playing, setPlaying] = useState(false);
  const [volume, setVolume] = useState(1);

  return (
    <Box
      alignItems="center"
      display="flex"
      height="100%"
      justifyContent="center"
      padding={8}
    >
      <Box width={500}>
        <Video
          aspectRatio={426 / 240}
          controls
          onControlsPause={() => setPlaying(false)}
          onControlsPlay={() => setPlaying(true)}
          onEnded={() => setPlaying(false)}
          onPlay={() => setPlaying(true)}
          onPlayError={({ error }) => error && setPlaying(false)}
          onVolumeChange={(e) => setVolume(e.volume)}
          playing={playing}
          src={[
            {
              type: 'video/mp4',
              src: 'https://archive.org/download/ElephantsDream/ed_1024_512kb.mp4',
            },
            {
              type: 'video/ogg',
              src: 'https://archive.org/download/ElephantsDream/ed_hd.ogv',
            },
          ]}
          volume={volume}
        />
      </Box>
    </Box>
  );
}

Component quality checklist

Component quality checklist
Quality item
Status
Status description
Figma Library
Ready
Component is available in Figma for web and mobile web.
Responsive Web
Ready
Component responds to changing viewport sizes in web and mobile web.

Internal documentation