Video Controls
- Stable
- WCAG 2.2 evidence
- RTL
The control bar of a video: scrubber, play / pause, back, mute and volume,
time, captions, settings, picture in picture and full screen, over a dark gradient. It drives nothing by itself: wire
the callbacks to a media element (or use VideoPlayer, which does). Accessibility: a named group of React Aria buttons and sliders, each in the tab order. Play / pause changes its name
("Play" / "Pause"); mute, captions, full screen and picture in picture are toggle buttons with aria-pressed and a bar
under the icon. The scrubber is a slider announcing "1:20 of 3:45"; arrows seek by seekStep, Page Up / Page Down by
10 %, Home / End to the ends, and pressing the track seeks there (no dragging needed). Shortcuts (K / Space, M, C, F,
arrows) work only while focus is in the controls and are listed in aria-keyshortcuts (WCAG 2.1.1, 2.1.4, 2.5.7,
4.1.2). Every target is at least 28 px (2.5.8). Controls are media/fg on a gradient that keeps 3:1 over any frame;
the brand fill and the 30 % rail are supplementary to the thumb and the time text (1.4.11). Consumer duties: captions for the media (1.2.2), a transcript or audio description where needed (1.2.3, 1.2.5), no
autoplay with sound (1.4.2), and translated labels.
import { VideoControls } from "@nexera-ui/react";- 5
- examples
- 30
- props
- 11
- live controls
- 1
- platform
- 9
- WCAG criteria
- 0
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { VideoControls } from "@nexera-ui/react";
<VideoControls />Examples 4
The same examples as Storybook, rendered live. Open Code to copy one.
Paused
Figma State=Paused: the play / pause button shows play and is named "Play".
import { VideoControls } from "@nexera-ui/react";
<VideoControls
duration={750}
defaultCurrentTime={252}
bufferedTime={375}
defaultPlaying
onCaptionsChange={() => undefined}
onSettingsPress={() => undefined}
onPictureInPictureChange={() => undefined}
onFullscreenChange={() => undefined}
/>;
Compact
Figma Layout=Compact: play / pause, mute, time and full screen, for narrow players.
import { VideoControls } from "@nexera-ui/react";
<div className="w-full max-w-sm">
<VideoControls
duration={750}
defaultCurrentTime={252}
bufferedTime={375}
defaultPlaying
onCaptionsChange={() => undefined}
onSettingsPress={() => undefined}
onPictureInPictureChange={() => undefined}
onFullscreenChange={() => undefined}
layout="compact"
/>
</div>;
Simulated playback
The controls wired to a simulated clock (no media), to try seeking, the back button and the shortcuts.
import { useEffect, useState } from "react";
import { VideoControls } from "@nexera-ui/react";
export function SimulatedPlayback() {
function Simulated() {
const [playing, setPlaying] = useState(false);
const [time, setTime] = useState(0);
useEffect(() => {
if (!playing) return undefined;
const timer = window.setInterval(() => {
setTime((current) => {
if (current + 1 >= args.duration) {
setPlaying(false);
return args.duration;
}
return current + 1;
});
}, 1000);
return () => {
window.clearInterval(timer);
};
}, [playing]);
return (
<VideoControls
duration={750}
defaultCurrentTime={252}
bufferedTime={375}
defaultPlaying
onCaptionsChange={() => undefined}
onSettingsPress={() => undefined}
onPictureInPictureChange={() => undefined}
onFullscreenChange={() => undefined}
isPlaying={playing}
onPlayingChange={setPlaying}
currentTime={time}
onSeek={setTime}
/>
);
}
return <Simulated />;
}
Narrow container
In a narrow container the button row wraps onto a second line instead of overflowing (WCAG 1.4.10).
import { VideoControls } from "@nexera-ui/react";
<div className="w-full max-w-xs">
<VideoControls
duration={750}
defaultCurrentTime={252}
bufferedTime={375}
defaultPlaying
onCaptionsChange={() => undefined}
onSettingsPress={() => undefined}
onPictureInPictureChange={() => undefined}
onFullscreenChange={() => undefined}
/>
</div>;
Props 30
Press "Try it" on a card to load that prop into the playground.
30 props shown
layoutNexera"full" | "compact"Which controls are shown. full: play / pause, back seekStep, mute and volume, time, captions,
settings, picture in picture, full screen. compact (narrow players): play / pause, mute, time, full screen.
isPlayingNexerabooleanWhether the media plays. Pair it with onPlayingChange.
defaultPlayingNexerabooleanWhether the media plays at first render (uncontrolled).
onPlayingChangeNexera(isPlaying: boolean) => voidCalled with the requested state when play / pause is pressed or K / Space is used. Play or pause your media.
duration*NexeranumberLength of the media in seconds. NaN or Infinity (unknown, live) disables the scrubber.
currentTimeNexeranumberPlayback position in seconds. Pair it with onSeek.
defaultCurrentTimeNexeranumberPlayback position at first render (uncontrolled).
onSeekNexera(seconds: number) => voidCalled with the position the user seeks to (scrubber, back button, arrow keys). Set your media's currentTime.
bufferedTimeNexeranumberHow far the media has buffered, in seconds.
volumeNexeranumberVolume from 0 to 1 (controlled). Pair it with onVolumeChange.
defaultVolumeNexeranumberVolume at first render (uncontrolled).
onVolumeChangeNexera(volume: number) => voidCalled with the new volume (volume slider, ArrowUp / ArrowDown).
isMutedNexerabooleanWhether the sound is muted (controlled). Pair it with onMutedChange.
defaultMutedNexerabooleanWhether the sound is muted at first render (uncontrolled).
onMutedChangeNexera(isMuted: boolean) => voidCalled with the new muted state (mute toggle, M). Raising the volume while muted unmutes.
isCaptionsOnNexerabooleanWhether captions are shown (controlled).
defaultCaptionsOnNexerabooleanWhether captions are shown at first render (uncontrolled).
onCaptionsChangeNexera(isOn: boolean) => voidCalled with the new captions state (captions toggle, C). The captions toggle (full) is shown when this is set.
isFullscreenNexerabooleanWhether the player is in full screen (controlled).
onFullscreenChangeNexera(isFullscreen: boolean) => voidCalled with the requested full-screen state (toggle, F). The full-screen toggle is shown when this is set.
isPictureInPictureNexerabooleanWhether picture in picture is on (controlled).
onPictureInPictureChangeNexera(isOn: boolean) => voidCalled with the requested picture-in-picture state. The toggle (full) is shown when this is set.
onSettingsPressNexera() => voidCalled when Settings is pressed: open your quality / speed menu. The button (full) is shown when this is set.
seekStepNexeranumberSeconds moved by ArrowLeft / ArrowRight (on the scrubber and as a shortcut) and by the back button.
keyboardShortcutsNexerabooleanKeyboard shortcuts while focus is in the controls: K / Space play and pause, M mute, C captions, F full screen,
arrows seek and change the volume. Never global; set false to turn them off (WCAG 2.1.4).
formatTimeNexera(seconds: number) => stringFormats a time for the Time text and the scrubber's announced value.
isDisabledNexerabooleanDisables every control (for example before the media has loaded).
labelsNexeraPartial<VideoControlsLabels>Accessible names and texts; English defaults. Translate them.
classNameNexerastringExtra classes for the root, merged last so they win over the defaults.
styleNexeraCSSPropertiesInline style for the root.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
7 direct · 2 supporting- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.4.2Audio ControlLevel A · tested directly
- 1.4.11Non-text ContrastLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.1.4Character Key ShortcutsLevel A · tested directly
- 2.4.7Focus VisibleLevel AA · tested directly
- 2.5.7Dragging MovementsLevel AA · tested directly
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-disableddata-draggingdata-hovereddata-presseddata-selected
<VideoControls className="data-disabled:opacity-90 shadow-sm" />Used in blocks
No block uses VideoControls yet.
Related components
- CarouselA carousel: slides in a scroll-snap strip with Previous / Next, indicators and, when you opt in, automatic rotation with a visible pause button.
- CarouselControlsPrevious / Next buttons for a carousel, and optionally a button that stops and starts automatic rotation.
- CarouselIndicatorShows which slide of a carousel is current, and lets people jump to a slide.
- ImageA picture in a rounded frame with a fixed aspect ratio: Figma `State=Loading` (a `bg/sunken` frame with a shimmer), `Loaded` and `Error` ("Image unavailable") follow the load lifecycle of the `<img>`; they are not props.
- ImageTileA square picture in a gallery or picker, with overlay actions on hover and a selected state.
- VideoPlayerA video with a poster, Nexera controls and captions.