Controls
Container component for composing and auto-hiding video player controls on user interaction
Import
import { Controls } from '@videojs/react';import '@videojs/html/ui/controls';Anatomy
Import the component and assemble its parts:
<Controls.Root>
<Controls.Backdrop />
<Controls.Content>
<Controls.Group />
</Controls.Content>
</Controls.Root><media-controls>
<media-controls-backdrop></media-controls-backdrop>
<media-controls-content>
<media-controls-group></media-controls-group>
</media-controls-content>
</media-controls>Behavior
If the user is active, or if the video is paused, this component will show controls. Otherwise, it will hide them after a short delay.
User activity is tracked via pointer movement, keyboard input, and focus events on the player container. On touch devices, a quick tap toggles visibility. mouseleave immediately sets the user as inactive.
Styling
Controls.Root is a state and context provider. Controls.Content / <media-controls-content> renders the interactive controls surface and receives its DOM props, ref, and controls state data attributes.
Controls.Backdrop / <media-controls-backdrop> is an optional presentational sibling of Content. It receives the same controls state data attributes, allowing its styling and transitions to be authored independently from the controls surface.
By default, controls have the following styles:
/* Click-through: clicks pass through controls to video beneath */
media-controls-content {
pointer-events: none;
}
media-controls-group {
pointer-events: auto;
}
media-controls-backdrop {
position: absolute;
inset: 0;
transition: opacity 0.35s;
}
/* Fade transition */
media-controls-content {
transition: opacity 0.25s;
}
media-controls-content:not([data-visible]) {
opacity: 0;
}
media-controls-backdrop:not([data-visible]) {
opacity: 0;
}React renders <div> elements. Add a className to style them:
/* Click-through: clicks pass through controls to video beneath */
.controls {
pointer-events: none;
}
.controls-group {
pointer-events: auto;
}
.controls-backdrop {
position: absolute;
inset: 0;
transition: opacity 0.35s;
}
/* Fade transition */
.controls {
transition: opacity 0.25s;
}
.controls:not([data-visible]) {
opacity: 0;
}
.controls-backdrop:not([data-visible]) {
opacity: 0;
}Accessibility
No ARIA role is applied to <media-controls> or <media-controls-content> — they provide state and layout, not a landmark. <media-controls-backdrop> is always hidden from assistive technology. <media-controls-group> automatically receives role="group" when an aria-label or aria-labelledby attribute is provided; otherwise no role is assigned.
Controls.Root renders no element. No ARIA role is applied to Controls.Content because it is a layout surface, not a landmark. Controls.Backdrop is always hidden from assistive technology. Controls.Group automatically receives role="group" when an aria-label or aria-labelledby attribute is provided; otherwise no role is assigned.
Examples
Basic Usage
import { Container, Controls, createPlayer, PlayButton, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const { Player } = createPlayer({ features: videoFeatures });
export default function BasicUsage() {
return (
<Player>
<Container className="media-container">
<Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop />
<Controls.Root>
<Controls.Backdrop className="controls-backdrop" />
<Controls.Content className="media-controls">
<Controls.Group className="controls-group" aria-label="Playback controls">
<PlayButton
className="button"
render={(props, state) => <button {...props}>{state.paused ? 'Play' : 'Pause'}</button>}
/>
<Time.Value type="current" className="time" />
</Controls.Group>
</Controls.Content>
</Controls.Root>
</Container>
</Player>
);
}
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.media-controls {
position: absolute;
inset: 0;
display: flex;
align-items: flex-end;
padding: 12px;
pointer-events: none;
transition: opacity 0.25s;
}
.controls-backdrop {
position: absolute;
inset: 0;
pointer-events: none;
background: linear-gradient(to top, rgba(0, 0, 0, 0.45), transparent 45%);
opacity: 1;
transition: opacity 0.35s;
}
.controls-backdrop:not([data-visible]),
.media-controls:not([data-visible]) {
opacity: 0;
}
.controls-group {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
pointer-events: auto;
}
.time {
display: inline-flex;
gap: 4px;
align-items: center;
padding-block: 8px;
padding-inline: 16px;
font-size: 14px;
color: black;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.button {
padding-block: 8px;
padding-inline: 16px;
font-size: 14px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
<video-player class="video-player">
<media-container>
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
<media-controls>
<media-controls-backdrop class="controls-backdrop"></media-controls-backdrop>
<media-controls-content class="media-controls">
<media-controls-group class="controls-group" aria-label="Playback controls">
<media-play-button class="button media-play-button">
<span class="paused">Play</span>
<span class="playing">Pause</span>
</media-play-button>
<media-time class="time" type="current"></media-time>
</media-controls-group>
</media-controls-content>
</media-controls>
</media-container>
</video-player>
.video-player media-container {
position: relative;
display: block;
}
.video-player media-container video {
width: 100%;
}
.media-controls {
position: absolute;
inset: 0;
display: flex;
align-items: flex-end;
padding: 12px;
pointer-events: none;
transition: opacity 0.25s;
}
.controls-backdrop {
position: absolute;
inset: 0;
pointer-events: none;
background: linear-gradient(to top, rgba(0, 0, 0, 0.45), transparent 45%);
opacity: 1;
transition: opacity 0.35s;
}
.controls-backdrop:not([data-visible]),
.media-controls:not([data-visible]) {
opacity: 0;
}
.controls-group {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
pointer-events: auto;
}
.time {
display: inline-flex;
gap: 4px;
align-items: center;
padding-block: 8px;
padding-inline: 16px;
font-size: 14px;
color: black;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.button {
padding-block: 8px;
padding-inline: 16px;
font-size: 14px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.button .paused,
.button .playing {
display: none;
}
.media-play-button[data-paused] .paused {
display: inline;
}
.media-play-button:not([data-paused]) .playing {
display: inline;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/controls';
import '@videojs/html/ui/play-button';
import '@videojs/html/ui/time';
API Reference
Rootmedia-controls
Manages controls state and provides it to the compound parts. Does not render an element.
Props
| Prop | Type | Default | Details |
|---|---|---|---|
visibility | 'auto' | 'always' | 'auto' | |
| |||
State
render, className, and style props.| Property | Type | Details |
|---|---|---|
visible | boolean | |
| ||
userActive | boolean | |
| ||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-visible | ||
| ||
data-user-active | ||
| ||
Backdropmedia-controls-backdrop
Presentational layer behind player controls. Renders a <div> with the controls state data attributes so skins can
style it without reaching across sibling components.
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: ControlsCore.State) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: ControlsCore.State) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: ControlsCore.State) => CSSProperties | undefined) | — | |
| |||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-visible | ||
| ||
data-user-active | ||
| ||
Contentmedia-controls-content
Renders the interactive controls surface.
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: ControlsCore.State) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: ControlsCore.State) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: ControlsCore.State) => CSSProperties | undefined) | — | |
| |||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-visible | ||
| ||
data-user-active | ||
| ||
Groupmedia-controls-group
Layout group for related controls; sets role="group" when labeled.
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: ControlsCore.State) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: ControlsCore.State) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: ControlsCore.State) => CSSProperties | undefined) | — | |
| |||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-visible | ||
| ||
data-user-active | ||
| ||