---
title: 'Why Video.js?'
description: 'The open-source video player for React and HTML. Lightweight, accessible components built for performance and streaming.'
---

import FrameworkCase from '@/components/docs/FrameworkCase.astro';
import DocsLink from '@/components/docs/DocsLink.astro';
import DocsLinkCard from '@/components/docs/DocsLinkCard.astro';
import ContentWidth from '@/components/frames/ContentWidth.astro';
import { TabsRoot, TabsList, TabsPanel, Tab } from '@/components/Tabs.tsx';
import chromeDefaultControls from '@/assets/docs/concepts/why-videojs/chrome-default-controls.png';
import safariDefaultControls from '@/assets/docs/concepts/why-videojs/safari-default-controls.png';
import firefoxDefaultControls from '@/assets/docs/concepts/why-videojs/firefox-default-controls.png';

Video.js is the open-source video player for the web. It makes web video easy, performant, accessible, and customizable. 

Let's dig into when you'd want to use Video.js, starting with the the question we hear most...


## Why not `<video>`?

The native `<video>` tag is great if you can describe your video as an image with a play button. Past that, the gaps show up quickly.

### Cross-browser inconsistency

Chrome, Safari, and Firefox each render the default `<video>` controls differently. You can hide them, but you can't deeply style or rearrange them. Building UI that matches your product means starting from scratch.

<ContentWidth margins="lg" class="grid grid-cols-3 gap-4">
  <figure>
    <img 
        src={chromeDefaultControls.src} 
        width={chromeDefaultControls.width} 
        height={chromeDefaultControls.height} 
        alt="Chrome's default video controls" 
        class="w-full h-auto"
    />
    <figcaption class="mt-2 text-center">Chrome</figcaption>
  </figure>
  <figure>
    <img 
        src={safariDefaultControls.src} 
        width={safariDefaultControls.width} 
        height={safariDefaultControls.height} 
        alt="Safari's default video controls" 
        class="w-full h-auto"
    />
    <figcaption class="mt-2 text-center">Safari</figcaption>
  </figure>
  <figure>
    <img 
        src={firefoxDefaultControls.src} 
        width={firefoxDefaultControls.width}
        height={firefoxDefaultControls.height} 
        alt="Firefox's default video controls" 
        class="w-full h-auto" 
    />
    <figcaption class="mt-2 text-center">Firefox</figcaption>
  </figure>
</ContentWidth>

### Video interactions

Video often involves a lot more than an MP4 in `src`. Rich playback features (like thumbnail previews, adaptive bitrate, quality selection, chapters, audio track switching, control localization, error UI, live-streaming UI, analytics, ads, 360 video, Chromecast, AirPlay, DRM, and more) are _possible_ with the video element but require complex wiring and deep consideration for maintainabilty and <DocsLink slug="concepts/accessibility">accessibility</DocsLink>.

Video.js aims to handle this all out of the box. (Though some features are still under development. Stay tuned!)

### Streaming formats and sources

Behind features like adaptive bitrate and quality selection are video formats like HLS or DASH -- videos broken into segments that can be selected according to bandwidth and preferences. Not every browser supports HLS, and DASH support is even more limited. Video.js closes the gap so the same `src` works everywhere.

Of course, your video might not be coming from a `src` like that. It might be provided by a service. However, services like YouTube, Vimeo, and Mux each speak their own API. Switching from a self-hosted file to a hosted service shouldn't mean switching players. Video.js abstracts the source so you can change where the video comes from without rewriting the surrounding code.

Video.js even considers sources like canvas-based MoQ or animated GIFs, all with the same components.

## Why Video.js?

Video.js is built at Mux by the teams behind Video.js, Plyr, Vidstack, and Media Chrome, with contributions from engineers across other player projects. Between us, our projects have served tens of billions of monthly video plays. Most of the choices on this page came from things we got wrong the first time, then the second, then the fifth. We've been supporting your edge cases for over a decade. Now we're shipping the player we always wanted to build.

Let's dig into some of the design principles that help explain why we built Video.js and why it may be the best choice for your project or team. 

### Add only what you need

Most player libraries ship every feature in one bundle, so you carry code you don't run. If your video doesn't need a feature like DRM, why ship that code? Video.js turns features into independent, _composable_ modules. You hand `createPlayer` the array you actually want, and what you don't import doesn't ship.

<FrameworkCase frameworks={["react"]}>
```tsx
import { createPlayer, playbackFeature, timeFeature } from '@videojs/react';

const Player = createPlayer({
  features: [playbackFeature, timeFeature], // [!code focus]
});
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```ts
import { createPlayer, playbackFeature, timeFeature } from '@videojs/html';

const { ProviderMixin, PlayerController, context } = createPlayer({
  features: [playbackFeature, timeFeature], // [!code focus]
});
```
</FrameworkCase>

<DocsLinkCard slug="concepts/features">Learn more about features</DocsLinkCard>

Of course, you don't have to think about every feature you're going to need if you don't want to. Pre-built feature bundles are still there if you'd rather not assemble the list by hand.

<DocsLinkCard slug="concepts/presets">Learn more about presets</DocsLinkCard>

### Framework-native

Many player libraries wrap a single web component for every framework. That works, until you reach the seams: refs you can't pass through, state that doesn't reconcile, prop names that aren't quite React or HTML. Video.js ships idiomatic APIs in each framework: React components and hooks for React, custom elements and controllers for HTML. (And we plan on supporting more frameworks in the futrue!) 

The player feels like the rest of your app.

<TabsRoot client:idle>
  <TabsList client:idle label="Framework-native APIs">
    <Tab client:idle value="react" initial>React</Tab>
    <Tab client:idle value="html">HTML</Tab>
  </TabsList>
  <TabsPanel client:idle value="react" initial>
    ```tsx
    import { Player, Video, VideoSkin } from '@videojs/react/video';

    function MyPlayer() {
      return (
        <Player.Provider>
          <VideoSkin>
            <Video src="movie.mp4" />
          </VideoSkin>
        </Player.Provider>
      );
    }
    ```
  </TabsPanel>
  <TabsPanel client:idle value="html">
    ```html
    <script type="module" src="@videojs/html/video/player"></script>

    <video-player>
      <video-skin>
        <video src="movie.mp4"></video>
      </video-skin>
    </video-player>
    ```
  </TabsPanel>
</TabsRoot>

### Eject and own

Most player libraries draw a line between "configurable through props" and "fork the source." Cross the line and you're on your own. Video.js lets you eject any skin and walk away with the source: components in your framework's language that you can read and change. The skin you ship is yours; the rest of the player keeps working underneath it.

<DocsLinkCard slug="how-to/customize-skins">Customize skins</DocsLinkCard>

### AI-native DX

Documentation aimed at human readers usually doesn't help an agent. Video.js publishes an `llms.txt` index, ships every docs page as both HTML and Markdown, and has a dedicated guide for building with AI assistants. Drop the player into a Claude or Cursor session and the docs come along for the ride.

<DocsLinkCard slug="how-to/build-with-ai">Build with AI</DocsLinkCard>
