FrameworkStyle

Architecture

Understanding the three-pillar architecture of Video.js v10+ - Player, Media, and UI

Video.js v10+ is built around three independent pillars— Player , Media , and UI/Skins —united by use cases .

Use Cases

Use cases are common scenarios: website players, background video, news embeds, streaming apps. Each use case defines what you need from each pillar:

Use Case Preset Skin Media
Website player presets.website() <FrostedSkin> Your choice
Background video presets.background() (no UI) Your choice
News embed presets.news() <NewsSkin> Your choice
Streaming app presets.streamingApp() <StreamingSkin> Your choice

Notice that Player and UI are tied together through use cases, while Media is independent —you choose HLS, DASH, native video, or a service like YouTube regardless of which player you’re using.

The Three Pillars

Player

The Player is the state and coordination root—think of it like an HTML <form> element. It doesn’t render UI or play media directly. Instead, it provides shared state and coordinates between Media and UI components inside it.

import { createPlayer, presets } from '@videojs/react';

const { Provider, usePlayer } = createPlayer(presets.website());

<Provider>
  {/* All components inside can access player state */}
</Provider>

In React, use cases are expressed through presets —configuration passed to createPlayer(). The resulting Provider component wraps your player tree.

Features bundle related state and requests (like Redux slices). Presets are curated collections of features:

  • website — playback, volume, time, fullscreen, keyboard, idle
  • background — playback only (autoplay, loop)
  • news — website features + ads, playlist (planned)
  • streamingApp — website features + quality selection, chapters (planned)
Learn more about presets →

Media

Media elements display and process your video or audio . They’re enhanced versions of the native <video> and <audio> elements, named by their implementation and media type.

// Native video
<Video src="video.mp4" />

// HLS streaming
<HlsVideo src="stream.m3u8" />

// DASH streaming
<DashVideo src="stream.mpd" />

// Service embeds
<YouTubeVideo videoId="dQw4w9WgXcQ" />
<VimeoVideo videoId="123456789" />

Media is independent of the Player and UI—you can use any media type with any player configuration. Media exposes the same API as the native video element, so UI components work regardless of what’s playing underneath.

Learn more about media →

UI/Skins

The UI pillar has two layers:

Skins — Complete, pre-designed player UIs that package components and styles together. Skins are standalone elements, tied to specific use cases:

  • <FrostedSkin> — Glassy design for website players
  • <MinimalSkin> — Clean, simple design for website players
Learn more about skins →

Components — Unstyled UI primitives in the media-* namespace. These bind via context to the nearest player/media:

  • Buttons (media-play-button, media-mute-button, media-fullscreen-button)
  • Sliders (media-time-slider, media-volume-slider)
  • Displays (media-current-time, media-duration)
  • Compound components (Tooltip, Popover)

Most people start with a skin and eject it for customization. Learn how to customize a skin →

Explore UI components →

Media and Feature Support

Not every media type supports every feature. For example, a native <video> playing an MP4 can’t support a quality selector—there’s only one quality. An HLS stream with multiple renditions can.

UI components can detect unsupported features and adapt (hiding the quality button when there’s nothing to select). But if you’re expecting a specific feature, make sure your media choice supports it:

Feature Native Video HLS DASH YouTube
Playback
Quality selection
Chapters
Live

How It All Works Together

import { createPlayer, presets, FrostedSkin, Video } from '@videojs/react';
import '@videojs/react/skins/frosted.css';

const { Provider } = createPlayer(presets.website());

function Player() {
  return (
    // Player: State coordination root
    <Provider>
      {/* UI: Skin for this use case */}
      <FrostedSkin>
        {/* Media: Independent choice */}
        <Video src="video.mp4" />
      </FrostedSkin>
    </Provider>
  );
}

Mix and Match

The three-pillar architecture gives you flexibility:

  • Use everything — Pick a use case, get a player + skin + media that work together
  • Swap the skin — Use a different skin with the same player
  • Swap the media — Use HLS instead of native video, same UI
  • Custom UI — Use the player for state, build your own UI with primitives
  • Headless — Use just the player layer for programmatic control, no UI

Design Notes

This section covers the reasoning behind architectural decisions. Skip this if you’re just getting started.

Why “Player” Instead of “Provider”?

The Player element is modeled after HTML’s <form>—a container that doesn’t render visible UI itself but provides shared state and coordination for everything inside it. Just as a form collects inputs and handles submission, a player collects media state and coordinates playback.

We chose “Player” over “Provider” because:

  • It matches what users expect (“I want a video player”)
  • It’s concrete rather than abstract
  • It parallels the <form> mental model

Why This Naming Structure?

React uses idiomatic patterns rather than encoding everything in names:

Concept Pattern
Use case createPlayer(presets.website()) — configuration
Skin <FrostedSkin> — component import
Media <HlsVideo> — component import
Primitives <PlayButton> — component import

Configuration via props and factory arguments is more natural in React than encoding use cases in component names. The conceptual model is identical—only the expression differs.

Why Do HTML and React Differ?

Both frameworks share the same concepts (Player = state root, Media = playback, Skins = UI), but express them naturally for each platform.

In HTML, the element name is the configuration. <website-video-player> is self-documenting and requires no build step to understand. Custom elements are the platform’s composition primitive.

In React, composition happens through imports and props. createPlayer(presets.website()) followed by <Provider> is idiomatic React. Encoding the preset in a component name would feel foreign.

The principle: start from the same concepts, diverge where it creates a more natural experience .

Why Are Skins Standalone Elements?

Skins aren’t CSS themes applied to a fixed structure—they’re complete component trees. Each skin defines its own layout, controls, and interactions.

This means:

  • A streaming skin can have completely different controls than a website skin
  • Skins can be optimized for their use case without compromise
  • Swapping skins is explicit, not accidental (no CSS specificity battles)

Why Is Media Independent?

Media elements (<hls-video>, <Video>) are decoupled from players and skins because:

  • The same UI should work regardless of what’s playing underneath
  • Media choice is often determined by content, not design
  • You can swap from MP4 to HLS without touching UI code

Media exposes the same API as native <video>, so UI components don’t need to know what implementation is underneath.

See Also

VideoJS