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