UI components
How Video.js UI components work — one element per component, data attributes for state, and compound composition.
UI components are controls like buttons, sliders, and time displays.
Every UI component renders exactly one HTML element, taking inspiration from projects like shadcn/ui and Base UI. This approach gives you control over styling and behavior while handling the complex interactions for you.
Where to put your components
UI Components can go anywhere inside a <Player.Provider>.
However, you should consider placing your components in <Player.Container>. Components in <Player.Container> will go fullscreen with the player, respond to user activity, and more.
UI Components can go anywhere inside a <video-player>.
However, you should consider placing your components in <player-container>. Components in <player-container> will go fullscreen with the player, respond to user activity, and more.
Styling and customization
The render prop
The render prop is the primary customization mechanism. It accepts a function that receives props and state, and returns your element. The props object includes event handlers, ARIA attributes, data attributes, and a ref — always spread {...props} to keep everything working:
<PlayButton
render={(props, state) => (
<button {...props}>
{state.paused ? 'Play' : 'Pause'}
</button>
)}
/> className and style also accept functions of state for dynamic styling without a full render prop:
<PlayButton
className={(state) =>
state.paused ? 'btn btn--paused' : 'btn btn--playing'
}
/>Data attributes and CSS custom properties
Components reflect player state as data-* attributes on their element. For example, data-paused or data-volume-level="high".
This lets you style state changes in pure CSS:
/* Show/hide icons based on play state */
.play-icon { display: none; }
.pause-icon { display: none; }
button[data-paused] .play-icon { display: inline; }
button:not([data-paused]) .pause-icon { display: inline; }Each component’s reference page documents its full set of data attributes.
Some components also expose CSS custom properties for continuous values like fill percentage and pointer position. Sliders, for example, set --media-slider-fill and --media-slider-pointer. See individual component reference pages for specifics.
Components reflect player state as data-* attributes on their element. For example, data-paused or data-volume-level="high".
This lets you style state changes in pure CSS:
<media-play-button class="my-play-button">
<span class="play-icon">Play</span>
<span class="pause-icon">Pause</span>
</media-play-button>media-play-button .play-icon { display: none; }
media-play-button .pause-icon { display: none; }
media-play-button[data-paused] .play-icon { display: inline; }
media-play-button:not([data-paused]) .pause-icon { display: inline; }Each component’s reference page documents its full set of data attributes.
Some components also expose CSS custom properties for continuous values like fill percentage and pointer position. Sliders, for example, set --media-slider-fill and --media-slider-pointer. See individual component reference pages for specifics.
Compound components
Complex interactions are split into composable parts. A parent manages shared state while children consume it. Each part is still one element.
Compound components use dot notation off a shared namespace:
<VolumeSlider.Root orientation="vertical">
<VolumeSlider.Track>
<VolumeSlider.Fill />
</VolumeSlider.Track>
<VolumeSlider.Thumb />
</VolumeSlider.Root><media-volume-slider orientation="vertical">
<media-slider-track>
<media-slider-fill></media-slider-fill>
</media-slider-track>
<media-slider-thumb></media-slider-thumb>
</media-volume-slider>