FrameworkStyle

Skins

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

<Player.Provider>
  <VideoSkin>
    {/* wraps the Media component */}
    <Video src="video.mp4"></Video>
  </VideoSkin>
</Player.Provider>

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

  <Player.Provider>
    <VideoSkin>
      {/* ...Media... */}
    </VideoSkin>
  </Player.Provider>

Example of ejected

  <Player.Provider>
    <Container className={cn('media-minimal-skin', className)} {...rest}>
      <BufferingIndicator
        render={(props) => (
          <div {...props} className="media-buffering-indicator">
            <SpinnerIcon className="media-icon" />
          </div>
        )}
      />

      <ErrorDialog
        aria-labelledby="media-error-title"
        aria-describedby="media-error-description"
        render={(props, { onDismiss }) => (
          <div {...props} className="media-error">
            <div className="media-error__dialog">
              <div className="media-error__content">
                <p id="media-error-title" className="media-error__title">
                  Something went wrong.
                </p>
                <p id="media-error-description" className="media-error__description">
                  An error occurred while trying to play the video. Please try again.
                </p>
              </div>
              <div className="media-error__actions">
                <Button onClick={onDismiss}>OK</Button>
              </div>
            </div>
          </div>
        )}
      />

      <Controls.Root className="media-controls">
        <span className="media-button-group">
          <PlayButton
            render={(props) => (
              <Button {...props} className="media-button--icon media-button--play">
                <RestartIcon className="media-icon media-icon--restart" />
                <PlayIcon className="media-icon media-icon--play" />
                <PauseIcon className="media-icon media-icon--pause" />
              </Button>
            )}
          />

          <SeekButton
            seconds={-SEEK_TIME}
            render={(props) => (
              <Button {...props} className="media-button--icon media-button--seek">
                <span className="media-icon__container">
                  <SeekIcon className="media-icon media-icon--seek media-icon--flipped" />
                  <span className="media-icon__label">{SEEK_TIME}</span>
                </span>
              </Button>
            )}
          />

          <SeekButton
            seconds={SEEK_TIME}
            render={(props) => (
              <Button {...props} className="media-button--icon media-button--seek">
                <span className="media-icon__container">
                  <SeekIcon className="media-icon media-icon--seek" />
                  <span className="media-icon__label">{SEEK_TIME}</span>
                </span>
              </Button>
            )}
          />
        </span>

        <span className="media-time-controls">
          <Time.Group className="media-time">
            <Time.Value type="current" className="media-time__value media-time__value--current" />
            <Time.Separator className="media-time__separator" />
            <Time.Value type="duration" className="media-time__value media-time__value--duration" />
          </Time.Group>

          <TimeSlider.Root className="media-slider">
            <TimeSlider.Track className="media-slider__track">
              <TimeSlider.Fill className="media-slider__fill" />
              <TimeSlider.Buffer className="media-slider__buffer" />
            </TimeSlider.Track>
            <TimeSlider.Thumb className="media-slider__thumb" />
          </TimeSlider.Root>
        </span>

        <span className="media-button-group">
          <PlaybackRateButton
            render={(props) => <Button {...props} className="media-button--icon media-button--playback-rate" />}
          />

          <Popover.Root openOnHover delay={200} closeDelay={100} side="top">
            <Popover.Trigger
              render={
                <MuteButton
                  render={(props) => (
                    <Button {...props} className="media-button--icon media-button--mute">
                      <VolumeOffIcon className="media-icon media-icon--volume-off" />
                      <VolumeLowIcon className="media-icon media-icon--volume-low" />
                      <VolumeHighIcon className="media-icon media-icon--volume-high" />
                    </Button>
                  )}
                />
              }
            />
            <Popover.Popup className="media-surface media-popup media-popup--volume media-popup-animation">
              <VolumeSlider.Root className="media-slider" orientation="vertical" thumbAlignment="edge">
                <VolumeSlider.Track className="media-slider__track">
                  <VolumeSlider.Fill className="media-slider__fill" />
                </VolumeSlider.Track>
                <VolumeSlider.Thumb className="media-slider__thumb media-slider__thumb--persistent" />
              </VolumeSlider.Root>
            </Popover.Popup>
          </Popover.Root>

          <CaptionsButton
            render={(props) => (
              <Button {...props} className="media-button--icon media-button--captions">
                <CaptionsOffIcon className="media-icon media-icon--captions-off" />
                <CaptionsOnIcon className="media-icon media-icon--captions-on" />
              </Button>
            )}
          />

          <PiPButton
            render={(props) => (
              <Button {...props} className="media-button--icon">
                <PipIcon className="media-icon" />
              </Button>
            )}
          />

          <FullscreenButton
            render={(props) => (
              <Button {...props} className="media-button--icon media-button--fullscreen">
                <FullscreenEnterIcon className="media-icon media-icon--fullscreen-enter" />
                <FullscreenExitIcon className="media-icon media-icon--fullscreen-exit" />
              </Button>
            )}
          />
        </span>
      </Controls.Root>

      {/* <div className="media-captions">
        <div className="media-captions__container">
          <span className="media-captions__text">An example cue</span>
          <span className="media-captions__text">
            <p>Another example cue with HTML</p>
          </span>
        </div>
      </div> */}

      <div className="media-overlay" />

      {children}
    </Container>
  </Player.Provider>

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. The features associated with a skin are called a feature bundle .

Feature bundle Available skins Import
videoFeatures <VideoSkin>, <MinimalVideoSkin> @videojs/react/video
audioFeatures <AudioSkin>, <MinimalAudioSkin> @videojs/react/audio
backgroundFeatures <BackgroundVideoSkin> @videojs/react/background

Want to learn more about skins and feature bundles? Check out the presets guide: