---
title: Video
description: A video in the page that plays on a press, from a file, a stream, or YouTube and Vimeo once the reader has agreed, or as a muted loop with a pause button.
visibility: guest
draft: false
---

# Video

`<x-wirekit::video>` plays a video in the page. A file or a stream plays when the reader presses play, and a video on YouTube or Vimeo loads nothing from the provider until the reader has agreed to it. With `background`, a file runs as a muted loop with a pause button.

## Basic Usage

Give the address, a title and the page's own still as the poster:

:::preview{title="A Clip That Plays on a Press"}
<x-wirekit::video
    src="/_assets/sample_clip_960x540.mp4"
    poster="/_assets/sample_1280x720_80p.jpg"
    title="A short sample clip"
/>
:::

Nothing loads before the press: the element gets its address then, and the poster is the only file the page fetches, lazily. After the press the browser's own controls take over, and the focus moves to the video, so the keyboard controls it at once.

The title names the play button, "Play A short sample clip", and the player of a provider. Without one the video is called "Video".

## YouTube and Vimeo

An address on YouTube or Vimeo plays in the provider's own player. Before the reader agrees, the page sends nothing to the provider: no player, no thumbnail, no script. A notice stands in its place, says which provider holds the video and that loading it sends data there, and offers "Load the video":

```blade
<x-wirekit::video :src="$video->url" :title="$video->title" />
```

YouTube plays from `www.youtube-nocookie.com`, the address on which it sets no cookie until the video plays, and Vimeo with Do Not Track switched on in the player's address, which keeps it from tracking the session. Every address form works: `youtube.com/watch?v=…`, `youtu.be/…`, `/embed/…`, `/shorts/…`, `vimeo.com/…` and `player.vimeo.com/video/…`, an unlisted Vimeo video with its hash included. An address on either host without the id of one video is reported and renders nothing.

A provider shows no poster of its own, since fetching its thumbnail is already a request to it. Give `poster` to show a still of your own behind the play button.

## Consent

The page decides whether the reader has agreed, and the player asks. Where the page knows it already, from the cookie of its consent manager, it says so with `consented`, and the play button loads the player:

```blade
<x-wirekit::video
    :src="$video->url"
    :title="$video->title"
    :poster="$video->poster_url"
    :consented="request()->cookie('consent_youtube') === 'yes'"
/>
```

Where it does not, the notice offers "Load the video". The press sends `wirekit:video-consent` from the player, and it bubbles. Its `detail` carries `name`, the name of the consent, and `provider`, `youtube` or `vimeo`:

- **Not canceled,** the press is the consent for this one video, and its player loads at once. Without a consent manager on the page this is all that happens, the two-click pattern.
- **Canceled,** your consent manager has taken the question. It asks in its own way and answers on `window` with `wirekit:consent`, whose `detail` carries `name` and `granted`.

```js
document.addEventListener('wirekit:video-consent', (event) => {
    // 1. Take the question from the player.
    event.preventDefault();

    // 2. Ask in your own way; confirm() stands in for your consent manager here.
    const granted = window.confirm(`Load videos from ${event.detail.provider}?`);

    // 3. Tell every player on the page what the reader chose.
    window.dispatchEvent(new CustomEvent('wirekit:consent', {
        detail: { name: event.detail.name, granted },
    }));
});
```

A player that waits for its consent loads the moment `granted` is `true`, and a player whose consent is withdrawn later, `granted: false`, removes the provider's player and shows its notice again. `consent` sets the name the player listens for, `youtube` or `vimeo` by default:

```blade
<x-wirekit::video :src="$video->url" :title="$video->title" consent="external-media" />
```

## A Stream

An address that ends in `.m3u8` is a stream (HLS), which changes its quality with the connection. Safari plays it as it is. Every other browser needs [hls.js](https://github.com/video-dev/hls.js). Load its own build on the page, which sets `window.Hls`, or install it and hand it over from your bundle:

```bash
npm install hls.js
```

```js
// resources/js/app.js
import Hls from 'hls.js';
import { registerHlsEngine } from '../../vendor/pushery/wirekit/dist/wirekit.esm.js';

registerHlsEngine(Hls);
```

A player that mounted before the library arrived plays once it is there. Where no browser and no library can play the stream, the player says so in a sentence instead of the video.

## Captions

A video with sound needs captions (WCAG 1.2.2). `tracks` takes them as the `<track>` elements of the video, each with `src` and, as it needs them, `srclang`, `label`, `kind` (`captions`, `subtitles` or `descriptions`, `captions` by default) and `default`:

```blade
<x-wirekit::video
    src="/videos/assembly.mp4"
    title="Assembling the shelf"
    :tracks="[
        ['src' => '/videos/assembly.en.vtt', 'srclang' => 'en', 'label' => 'English', 'default' => true],
        ['src' => '/videos/assembly.de.vtt', 'srclang' => 'de', 'label' => 'Deutsch'],
    ]"
/>
```

The track marked `default` shows from the start, and the browser's controls switch between them. A video on YouTube or Vimeo carries the captions the provider has for it.

## A Muted Loop

With `background` a file or a stream plays muted and looped, without controls, as the backdrop of a section. It starts when a quarter of it is in view and pauses when it leaves. A button in its corner pauses it (WCAG 2.2.2):

:::preview{title="A Muted Loop With Its Pause Button"}
<x-wirekit::video
    src="/_assets/sample_clip_960x540.mp4"
    poster="/_assets/sample_1280x720_80p.jpg"
    title="A short sample clip"
    background
/>
:::

A reader who asked for less motion (`prefers-reduced-motion: reduce`) or to save data (`Save-Data`) sees the poster, and the button plays the loop if they want it. Once the reader paused it, the loop stays paused when it comes back into view. A loop whose file fails after it started, such as a response that breaks off, stops and shows the poster again, and the button offers to play it. A provider's player cannot be a loop, and `background` on one is reported.

## Ratio

The box holds its ratio before anything loads, 16 by 9 unless `ratio` says otherwise: `16/9`, `4/3`, `1/1`, `9/16` for an upright phone video, or `21/9`. A video of another shape is fitted inside the box, and the poster covers it:

:::preview{title="A Wide Box"}
<x-wirekit::video
    src="/_assets/sample_clip_960x540.mp4"
    poster="/_assets/sample_1280x548_80p.jpg"
    title="A short sample clip"
    ratio="21/9"
/>
:::

## Content Security Policy

A page with a `frame-src` directive allows the two hosts the players load from, `www.youtube-nocookie.com` and `player.vimeo.com`. A stream played through hls.js also needs its host in `connect-src` and `media-src`, with `blob:` in `media-src`.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `src` | `string` | — | A file, a stream (`.m3u8`) or a video on YouTube or Vimeo. Without one nothing renders, and it is reported |
| `title` | `string\|null` | "Video" | The video's name: the play button's and the provider player's |
| `poster` | `string\|null` | `null` | Your own still, shown until the video plays |
| `ratio` | `string` | `'16/9'` | `16/9`, `4/3`, `1/1`, `9/16` or `21/9` |
| `tracks` | `array` | `[]` | Captions and subtitles. See [Captions](#captions) |
| `background` | `bool` | `false` | A muted loop with a pause button. See [A Muted Loop](#a-muted-loop) |
| `consented` | `bool` | `false` | Whether the page holds the reader's consent for the provider. See [Consent](#consent) |
| `consent` | `string\|null` | the provider | The name of the consent the player listens for |
| `scope` | `string\|null` | `null` | Scoped personalization key |

## Events

| Event | Detail | When |
| --- | --- | --- |
| `wirekit:video-consent` | `{ name, provider }` | The reader pressed "Load the video". Cancel it to answer with `wirekit:consent` yourself |

The player listens on `window` for `wirekit:consent`, `{ name, granted }`, which your consent manager sends. See [Consent](#consent).

## Accessibility

- The play button is named after the video, "Play A short sample clip", and the player of a provider takes the title as its name.
- After the press the focus goes to the video or to the provider's player, so the keyboard reaches its controls without a search.
- The notice says which provider holds the video and that loading it sends data there, so the choice is an informed one.
- The button of a loop names what it does now, "Pause the video" or "Play the video", and stays in the tab order. A loop never starts under `prefers-reduced-motion`.
- Captions from `tracks` show from the start where one is marked `default`.

## Keyboard Interaction

| Key | Action |
| --- | --- |
| `Tab` | Moves to the play button, the notice's button or the button of a loop |
| `Enter` / `Space` | Presses it. After the play button, the video or the provider's player has the focus |
