Skip to content
FrameworkStyle

Skins

Packaged player designs that include both UI components and their styles.

<video-player>
  <video-skin>
    <!-- wraps the media element -->
    <video src="video.mp4"></video>
  </video-skin>
</video-player>

Packaged vs. ejected

When you choose a skin you have two options for how you use it: packaged or ejected . It’s usually easiest to start with a packaged skin and later eject its internal components into your project when you need more customization.

Packaged Ejected
Single component Many UI components
Limited customization Complete customization
Future design updates auto-applied by bumping the version Future design updates manually applied, or intentionally ignored

Example of packaged

  <video-player>
    <video-skin>
      <!--...Media...-->
    </video-skin>
  </video-player>

Example of ejected

<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/video-ui.js"></script>
<link rel="stylesheet" href="./player.css">

<video-player>
  <media-container class="media-default-skin media-default-skin--video">
    <video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" playsinline></video>

    <media-poster>
      <img src="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp" />
    </media-poster>

    <media-buffering-indicator class="media-buffering-indicator">
      <div class="media-surface">
        <media-icon name="spinner" class="media-icon"></media-icon>
      </div>
    </media-buffering-indicator>

    <media-error-dialog class="media-error">
      <div class="media-error__dialog media-surface">
        <div class="media-error__content">
          <media-alert-dialog-title class="media-error__title">Something went wrong.</media-alert-dialog-title>
          <media-alert-dialog-description class="media-error__description"></media-alert-dialog-description>
        </div>
        <div class="media-error__actions">
          <media-alert-dialog-close class="media-button media-button--primary">OK</media-alert-dialog-close>
        </div>
      </div>
    </media-error-dialog>

    <media-controls class="media-surface media-controls">
      <media-tooltip-group>
        <div class="media-button-group">
          <media-play-button commandfor="play-tooltip" class="media-button media-button--subtle media-button--icon media-button--play">
            <media-icon name="restart" class="media-icon media-icon--restart"></media-icon>
            <media-icon name="play" class="media-icon media-icon--play"></media-icon>
            <media-icon name="pause" class="media-icon media-icon--pause"></media-icon>
          </media-play-button>
          <media-tooltip id="play-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>

          <media-seek-button commandfor="seek-backward-tooltip" seconds="-10" class="media-button media-button--subtle media-button--icon media-button--seek">
            <span class="media-icon__container">
              <media-icon name="seek" class="media-icon media-icon--flipped"></media-icon>
              <span class="media-icon__label">10</span>
            </span>
          </media-seek-button>
          <media-tooltip id="seek-backward-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>

          <media-seek-button commandfor="seek-forward-tooltip" seconds="10" class="media-button media-button--subtle media-button--icon media-button--seek">
            <span class="media-icon__container">
              <media-icon name="seek" class="media-icon"></media-icon>
              <span class="media-icon__label">10</span>
            </span>
          </media-seek-button>
          <media-tooltip id="seek-forward-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>
        </div>

        <div class="media-time-controls">
          <media-time type="current" class="media-time"></media-time>
          <media-time-slider class="media-slider">
            <media-slider-track class="media-slider__track">
              <media-slider-fill class="media-slider__fill"></media-slider-fill>
              <media-slider-buffer class="media-slider__buffer"></media-slider-buffer>
            </media-slider-track>
            <media-slider-thumb class="media-slider__thumb"></media-slider-thumb>

            <div class="media-surface media-preview media-slider__preview">
              <media-slider-thumbnail class="media-preview__thumbnail"></media-slider-thumbnail>
              <media-slider-value type="pointer" class="media-time media-preview__time"></media-slider-value>
              <media-icon name="spinner" class="media-preview__spinner media-icon"></media-icon>
            </div>
          </media-time-slider>
          <media-time type="duration" class="media-time"></media-time>
        </div>

        <div class="media-button-group">
          <media-playback-rate-button commandfor="playback-rate-tooltip"  class="media-button media-button--subtle media-button--icon media-button--playback-rate"></media-playback-rate-button>
          <media-tooltip id="playback-rate-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>

          <media-mute-button commandfor="video-volume-popover" class="media-button media-button--subtle media-button--icon media-button--mute">
            <media-icon name="volume-off" class="media-icon media-icon--volume-off"></media-icon>
            <media-icon name="volume-low" class="media-icon media-icon--volume-low"></media-icon>
            <media-icon name="volume-high" class="media-icon media-icon--volume-high"></media-icon>
          </media-mute-button>

          <media-popover id="video-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="media-surface media-popover media-popover--volume">
            <media-volume-slider class="media-slider" orientation="vertical" thumb-alignment="edge">
              <media-slider-track class="media-slider__track">
                <media-slider-fill class="media-slider__fill"></media-slider-fill>
              </media-slider-track>
              <media-slider-thumb class="media-slider__thumb media-slider__thumb--persistent"></media-slider-thumb>
            </media-volume-slider>
          </media-popover>

          <media-captions-button commandfor="captions-tooltip" class="media-button media-button--subtle media-button--icon media-button--captions">
            <media-icon name="captions-off" class="media-icon media-icon--captions-off"></media-icon>
            <media-icon name="captions-on" class="media-icon media-icon--captions-on"></media-icon>
          </media-captions-button>
          <media-tooltip id="captions-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>

          <media-cast-button commandfor="cast-tooltip" class="media-button media-button--subtle media-button--icon media-button--cast">
            <media-icon name="cast-enter" class="media-icon media-icon--cast-enter"></media-icon>
            <media-icon name="cast-exit" class="media-icon media-icon--cast-exit"></media-icon>
          </media-cast-button>
          <media-tooltip id="cast-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>

          <media-pip-button commandfor="pip-tooltip" class="media-button media-button--subtle media-button--icon media-button--pip">
            <media-icon name="pip-enter" class="media-icon media-icon--pip-enter"></media-icon>
            <media-icon name="pip-exit" class="media-icon media-icon--pip-exit"></media-icon>
          </media-pip-button>
          <media-tooltip id="pip-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>

          <media-fullscreen-button commandfor="fullscreen-tooltip" class="media-button media-button--subtle media-button--icon media-button--fullscreen">
            <media-icon name="fullscreen-enter" class="media-icon media-icon--fullscreen-enter"></media-icon>
            <media-icon name="fullscreen-exit" class="media-icon media-icon--fullscreen-exit"></media-icon>
          </media-fullscreen-button>
          <media-tooltip id="fullscreen-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>
        </div>
      </media-tooltip-group>
    </media-controls>

    <div class="media-overlay"></div>

    <!-- Hotkeys -->
    <media-hotkey keys="Space" action="togglePaused"></media-hotkey>
    <media-hotkey keys="k" action="togglePaused"></media-hotkey>
    <media-hotkey keys="m" action="toggleMuted"></media-hotkey>
    <media-hotkey keys="f" action="toggleFullscreen"></media-hotkey>
    <media-hotkey keys="c" action="toggleSubtitles"></media-hotkey>
    <media-hotkey keys="i" action="togglePictureInPicture"></media-hotkey>
    <media-hotkey keys="ArrowRight" action="seekStep" value="5"></media-hotkey>
    <media-hotkey keys="ArrowLeft" action="seekStep" value="-5"></media-hotkey>
    <media-hotkey keys="l" action="seekStep" value="10"></media-hotkey>
    <media-hotkey keys="j" action="seekStep" value="-10"></media-hotkey>
    <media-hotkey keys="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
    <media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></media-hotkey>
    <media-hotkey keys="0-9" action="seekToPercent"></media-hotkey>
    <media-hotkey keys="Home" action="seekToPercent" value="0"></media-hotkey>
    <media-hotkey keys="End" action="seekToPercent" value="100"></media-hotkey>
    <media-hotkey keys=">" action="speedUp"></media-hotkey>
    <media-hotkey keys="<" action="speedDown"></media-hotkey>

    <!-- Gestures -->
    <media-gesture type="tap" action="togglePaused" pointer="mouse" region="center"></media-gesture>
    <media-gesture type="tap" action="toggleControls" pointer="touch"></media-gesture>
    <media-gesture type="doubletap" action="seekStep" value="-10" region="left"></media-gesture>
    <media-gesture type="doubletap" action="toggleFullscreen" region="center"></media-gesture>
    <media-gesture type="doubletap" action="seekStep" value="10" region="right"></media-gesture>
  </media-container>
</video-player>

Skins, features, and presets

Each skin is built with specific features in mind. For example, a video skin renders fullscreen and picture-in-picture controls. An audio skin doesn’t.

You’ll find both a skin and the feature bundle it expects exported from the same path. We call these paths presets .

Import Description Details
@videojs/html/video General-purpose video player preset with full playback controls.
@videojs/html/audio Audio-only player preset with playback and volume controls.
@videojs/html/background Ambient background video preset with no user controls.
@videojs/html/live-audio Live audio player preset — same features as audio with a skin that omits duration / current-time displays.
@videojs/html/live-video Live video player preset — same features as video with a skin that omits duration / current-time displays.

Presets are a topic quite a bit bigger than just this guide. To learn more, check out the guide:

Styling

There are currently two options for styling:

  • Vanilla CSS that’s automatically imported. This is the default.
  • Tailwind where you eject the skin and use Tailwind classnames in your app.

Current limitations

  • In both style systems we assume a 16px root font size and we use rem units for sizing.
  • The default font stack includes Inter (because it’s awesome) but we do not load the webfonts for you. If they are not available then system fonts are used.

These only apply to ejected HTML and React skins:

Vanilla CSS

  • We use a BEM classname structure and every component classname is scoped with a media- prefix.

Tailwind

  • Currently we’re assuming you’re using the default configuration and that’s all that’s supported. With the release of our CLI, this will change, allowing you to specify a custom prefix to the Tailwind classnames. For now, you’ll need to edit the ejected skins yourself.
  • We’re assuming the latest version, currently 4.2.x.