# media-airplay-button

Accessible AirPlay toggle button that opens the WebKit playback target picker and reflects session state

## Import

```ts
import '@videojs/html/ui/airplay-button';
```

## Anatomy

```html
<media-airplay-button></media-airplay-button>
```

## Behavior

Opens the WebKit AirPlay playback target picker and reflects session state. AirPlay is a WebKit-only feature, so the button reports `availability: "unsupported"` outside Safari (macOS and iOS). On supported platforms availability flips to `"available"` once Safari discovers at least one AirPlay receiver on the local network, and `"unavailable"` otherwise.

The button is hidden until `availability` is `"available"`: the element receives the native `hidden` attribute.

An explicitly disabled, otherwise available button stays visible and focusable with `aria-disabled="true"` and `data-disabled`, but does not open the picker.

The component consumes the unified [`remotePlayback`](../api/feature-remote-playback.md) store feature alongside `<media-cast-button>`: both buttons drive their state from the same feature, but each only surfaces on its supported platform (WebKit for AirPlay, Chromium for Cast).

WebKit does not expose a `"connecting"` intermediate state — the `state` slice flips directly between `"disconnected"` and `"connected"` when the AirPlay session changes. When connected, the picker itself acts as the disconnect UI.

## Styling

| Attribute | Values | Description |
| --- | --- | --- |
| `data-airplay-state` | `"disconnected"` \| `"connecting"` \| `"connected"` | Current AirPlay session state |
| `data-availability` | `"available"` \| `"unavailable"` \| `"unsupported"` | Whether AirPlay is reachable on the current platform |
| `data-disabled` | Present / absent | Present when the button is non-interactive |
| `data-hidden` | Present / absent | Present on the HTML element while AirPlay is unavailable or unsupported |

Use `data-airplay-state` to swap icons or labels based on the session state:

```css
/* AirPlay active */
media-airplay-button[data-airplay-state="connected"] {
  color: var(--accent);
}
```

Use `data-disabled` to style an explicitly disabled, available button:

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

Unavailable and unsupported buttons are hidden automatically. No availability selector or extra hiding CSS is required.

## Accessibility

Renders a `<button>` with an automatic `aria-label`: “Start AirPlay” when disconnected, “Stop AirPlay” when connected. (The component also supports a `"Connecting"` label, but WebKit AirPlay never emits a `connecting` state, so that label is unreachable in practice.) Override with the `label` prop — either a string or a function that receives the current state. Keyboard activation: Enter / Space.

## Examples

### Basic Usage

**index.html**

```html
<video-player class="video-player">
  <media-container>
    <video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
    <media-airplay-button class="media-airplay-button">
      <span class="connected">Stop AirPlay</span>
      <span class="not-connected">Start AirPlay</span>
    </media-airplay-button>
  </media-container>
</video-player>
```

**index.css**

```css
.video-player media-container {
  position: relative;
  display: block;
}

.video-player media-container video {
  width: 100%;
}

.media-airplay-button {
  position: absolute;
  right: 10px;
  bottom: 10px;
  padding-block: 8px;
  padding-inline: 20px;
  color: black;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 9999px;
  backdrop-filter: blur(10px);
}

.media-airplay-button .connected,
.media-airplay-button .not-connected {
  display: none;
}

/* Connected: AirPlay active */
.media-airplay-button[data-airplay-state="connected"] .connected {
  display: inline;
}

/* Not connected: ready to AirPlay */
.media-airplay-button:not([data-airplay-state="connected"]) .not-connected {
  display: inline;
}

/* Explicitly disabled while AirPlay is available. */
.media-airplay-button[data-disabled] {
  cursor: not-allowed;
  opacity: 0.5;
  filter: grayscale(1);
}
```

**index.ts**

```ts
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/airplay-button';
```

## API Reference

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | Whether the button is disabled. |
| `label` | `{ key: string; text: string } \| string \| ((state: AirPlayButtonState) => Text \| string)` | `''` | Custom label for the button. |

### State

State is reflected as data attributes for CSS styling.

| Property | Type | Description |
| --- | --- | --- |
| `label` | `{ key: string; text: string } \| string` | |
| `state` | `'disconnected' \| 'connecting' \| 'connected'` | Current AirPlay connection state. |
| `availability` | `'available' \| 'unavailable' \| 'unsupported'` | Whether AirPlay is available on the active platform and media. |
| `disabled` | `boolean` | Non-interactive but still focusable (mirrors `aria-disabled`). |
| `hidden` | `boolean` | Whether the button is hidden until AirPlay is available. |

### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-airplay-state` | `'disconnected' \| 'connecting' \| 'connected'` | Current AirPlay connection state. |
| `data-availability` | `'available' \| 'unavailable' \| 'unsupported'` | Whether AirPlay is available on the active platform and media. |
| `data-disabled` | — | Present when the button is non-interactive (mirrors `aria-disabled`). |
| `data-hidden` | — | Present when the button is hidden because AirPlay is unavailable. |