# Features

The state and actions each feature adds to the player

Not every player needs to support every use case. To address this, Video.js has a concept of **features** — self-contained units of player functionality. Each one adds state properties and actions to the player. For example, “playback” adds `paused` and `play()`, “volume” adds `volume` and `setVolume()`, and so on.

## Feature bundles and presets

You probably don’t want to hand-pick every feature your player needs. A **feature bundle** groups the features needed for a specific use case into a single array.

For example, if you import `<video-player>` from the `/video` path, you will have playback, volume, time, fullscreen, and more already enabled.

```html
<script type="module">
  import '@videojs/html/video/player';
  import '@videojs/html/video/skin';
</script>
<video-player>
  <video-skin>
    <video src="movie.mp4"></video>
  </video-skin>
</video-player>
```

Feature bundles are usually paired with specific skins and media elements in a **preset**. To learn more about this, and the available presets, you’ll want to check out the presets guide.

- [Learn more about presets](https://videojs.org/docs/framework/html/guides/presets)

## Individual features

If you’re not using presets, you can create a player that has individual features.

```ts
import { controlsFeature, createPlayer, playbackFeature, timeFeature, volumeFeature } from '@videojs/html';

const { PlayerElement: MyPlayer } = createPlayer({
  features: [playbackFeature, volumeFeature, timeFeature, controlsFeature],
});

customElements.define('my-player', MyPlayer);
```

## Extended feature bundles

Since feature bundles are just an array of features under the hood, it doesn’t take much to extend one. For example, if you were using the `/background` preset and wanted a play button on your background video, you might add the playback feature.

```ts
import { createPlayer, playbackFeature } from '@videojs/html';
import { backgroundFeatures } from '@videojs/html/background';

const { PlayerElement: MyPlayer } = createPlayer({
  features: [...backgroundFeatures, playbackFeature],
});

customElements.define('my-player', MyPlayer);
```

## Features inside components

You can access feature state and actions through [`PlayerController`](https://videojs.org/docs/framework/html/reference/api/player-controller) and pre-built feature selectors (e.g. [`selectPlayback`](https://videojs.org/docs/framework/html/reference/api/feature-playback), [`selectVolume`](https://videojs.org/docs/framework/html/reference/api/feature-volume)):

```ts
import { selectPlayback } from '@videojs/html';

// Subscribe to feature state (triggers element updates on change)
readonly #playback = new PlayerController(this, selectPlayback);

// Read state and call actions
const media = this.#playback.value;
if (media?.paused) media.play();
```

## Feature availability

Volume, fullscreen, and picture-in-picture expose availability properties because platform support varies. Volume has two independent properties: `volumeAvailability` for changing the volume level and `mutedAvailability` for toggling mute. For example, iOS Safari allows muting but doesn’t allow programmatic volume control.

| Value | Meaning |
| --- | --- |
| `'available'` | Ready to use |
| `'unavailable'` | Could work, not ready yet |
| `'unsupported'` | Platform can never do this |

Components that depend on availability (like [`<media-mute-button>`](https://videojs.org/docs/framework/html/reference/components/mute-button), [`<media-pip-button>`](https://videojs.org/docs/framework/html/reference/components/pip-button), and [`<media-fullscreen-button>`](https://videojs.org/docs/framework/html/reference/components/fullscreen-button)) handle the three states for you:

- **`unsupported`** — the element receives the native `hidden` attribute (and `data-hidden`). No CSS needed.
- **`unavailable`** — the presentation controls stay hidden while capability detection is unresolved. Cast remains visible but disabled when its API is supported and no device is reachable.
- **`disabled` attribute** — an available button stays visible and focusable with `aria-disabled="true"` plus `data-disabled` for styling.
- **`available`** — fully interactive.

Use `data-disabled` to style the non-interactive state:

```css
media-pip-button[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}
```

You can also check availability in JS:

```ts
const media = this.#pip.value;

if (media?.pipAvailability !== 'available') {
  this.hidden = true;
}
```

---

HTML documentation: https://videojs.org/docs/framework/html/llms.txt
All documentation: https://videojs.org/llms.txt
