Props
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 padding={8} height="100%" display="flex" alignItems="center" justifyContent="center" > <Box width={1000}> <Video aspectRatio={1024 / 435} captions="https://iandevlin.github.io/mdn/video-player-with-captions/subtitles/vtt/sintel-en.vtt" controls playing={playing} volume={volume} onPlay={() => setPlaying(true)} onPlayError={({ error }) => error && setPlaying(false)} onControlsPlay={() => setPlaying(true)} onControlsPause={() => setPlaying(false)} onEnded={() => setPlaying(false)} onVolumeChange={(e) => setVolume(e.volume)} loop src="https://iandevlin.github.io/mdn/video-player-with-captions/video/sintel-short.mp4" /> </Box> </Box> ); }
Labels
Video requires several accessibility labels for each video control: accessibilityMaximizeLabel
, accessibilityMinimizeLabel
, accessibilityMuteLabel
, accessibilityPauseLabel
, accessibilityPlayLabel
, accessibilityProgressBarLabel
and accessibilityUnmuteLabel
.
If the video contain captions, it also requires accessibilityHideCaptionsLabel
and accessibilityShowCaptionsLabel
.
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 padding={8} height="100%" display="flex" alignItems="center" justifyContent="center" > <Box width={300}> <Video autoplay aspectRatio={540 / 960} controls onPlayError={({ error }) => error && setPlaying(false)} onPlay={() => setPlaying(true)} onControlsPlay={() => setPlaying(true)} onControlsPause={() => setPlaying(false)} onEnded={() => setPlaying(false)} onVolumeChange={(e) => setVolume(e.volume)} playing={playing} volume={volume} loop src="https://v.pinimg.com/videos/mc/expMp4/c8/37/71/c83771d856bc1ee12e2d2f81083df9d4_t1.mp4" /> </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 padding={8} height="100%" display="flex" alignItems="center" justifyContent="center" > <Flex alignItems="center" gap={{ column: 2, row: 0 }} direction="column"> <Flex alignItems="center" gap={{ row: 2, column: 0 }}> <Box paddingX={2}> <Label htmlFor="video"> <Text>Show built-in Video controls</Text> </Label> </Box> <Switch onChange={() => setShowControls((value) => !value)} id="video" switched={showControls} /> </Flex> <Box width={300}> <Video aspectRatio={540 / 960} controls={showControls} playing={playing} volume={volume} onPlay={() => setPlaying(true)} onPlayError={({ error }) => error && setPlaying(false)} onControlsPlay={() => setPlaying(true)} onControlsPause={() => setPlaying(false)} onEnded={() => setPlaying(false)} onVolumeChange={(e) => setVolume(e.volume)} 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" > {!showControls ? ( <Box width="100%" height="100%" display="flex" justifyContent="center" alignItems="center" dangerouslySetInlineStyle={{ __style: { backgroundColor: 'rgba(0, 0, 0, 0.3)' }, }} > <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 padding={8} height="100%" display="flex" alignItems="center" justifyContent="center" > <Box width={300}> <Video aspectRatio={540 / 960} controls src="https://v.pinimg.com/videos/mc/expMp4/c8/37/71/c83771d856bc1ee12e2d2f81083df9d4_t1.mp4" playing={playing} volume={volume} onPlay={() => setPlaying(true)} onPlayError={({ error }) => error && setPlaying(false)} onControlsPlay={() => setPlaying(true)} onControlsPause={() => setPlaying(false)} onEnded={() => setPlaying(false)} onVolumeChange={(e) => setVolume(e.volume)} > <Box width="100%" height="100%" display="flex" justifyContent="center" alignItems="center" dangerouslySetInlineStyle={{ __style: { backgroundColor: 'rgba(0, 0, 0, 0.3)' }, }} > <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 padding={8} height="100%" display="flex" justifyContent="center"> <Flex width="100%" gap={{ column: 2, row: 0 }} direction="column"> <Label htmlFor="video-source"> <Text>Video source URL</Text> </Label> <Flex width="100%" gap={{ row: 2, column: 0 }} alignItems="center"> <Flex.Item flex="grow"> <TextField id="video-source" onChange={({ value }) => setInput(value)} value={input} /> </Flex.Item> <Button text="Submit" color="red" onClick={() => setSrc(input)} /> </Flex> <Flex gap={{ column: 0, row: 2 }}> <Button text={volume === 0 ? 'Unmute' : 'Mute'} onClick={() => setVolume(volume === 0 ? 1 : 0)} /> <Button text="Playback x0.5" onClick={() => setPlaybackRate((value) => (value >= 1 ? value / 2 : 0.5)) } /> <Button text="Playback x2" onClick={() => setPlaybackRate((value) => (value < 8 ? value * 2 : 16)) } /> </Flex> <Box width={300}> <Video aspectRatio={540 / 960} controls onControlsPlay={() => setPlaying(true)} onControlsPause={() => setPlaying(false)} onVolumeChange={(e) => setVolume(e.volume)} onPlay={() => setPlaying(true)} onPlayError={({ error }) => error && setPlaying(false)} playbackRate={playbackRate} playing={playing} src={src} loop 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 padding={8} height="100%" display="flex" alignItems="center" justifyContent="center" > <Box width={500}> <Video aspectRatio={426 / 240} controls playing={playing} volume={volume} onPlay={() => setPlaying(true)} onPlayError={({ error }) => error && setPlaying(false)} onControlsPlay={() => setPlaying(true)} onControlsPause={() => setPlaying(false)} onEnded={() => setPlaying(false)} onVolumeChange={(e) => setVolume(e.volume)} 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', }, ]} /> </Box> </Box> ); }
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. |