Skip to content
FrameworkStyle

UI components

How Video.js UI components use focused elements, data attributes for state, and compound composition.

UI components are controls like buttons, sliders, and time displays.

Visible UI components render at most 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. State-only components render no markup, and controls can skip rendering when their feature is unavailable.

Individual controls provide interaction, accessible names, and state. You add their visible text or icons and CSS. Sliders also need the child elements and styles shown on their reference pages.

Where to put your components

UI components can go anywhere inside a <Player>.

However, you should consider placing your components in <Container>. Components in <Container> will go fullscreen with the player, respond to user activity, and more.

Styling and customization

For components you add yourself, use the documented props, state data-* attributes, and CSS custom properties. Add your own className when you need a selector.

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:

import { PlayButton } from '@videojs/react';
import { PauseIcon, PlayIcon } from '@videojs/react/icons';

<PlayButton
  render={(props, state) => (
    <button {...props}>
      {state.paused ? <PlayIcon /> : <PauseIcon />}
    </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.

Compound components

Complex interactions are split into composable parts. A parent manages shared state while children consume it. Each visible part owns at most 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>