# media-volume-slider

A slider component for controlling media playback volume

## Import

```ts
import '@videojs/html/ui/volume-slider';
import '@videojs/html/ui/slider-track';
import '@videojs/html/ui/slider-fill';
import '@videojs/html/ui/slider-thumb';
import '@videojs/html/ui/slider-preview';
import '@videojs/html/ui/slider-value';
```

## Anatomy

```html
<media-volume-slider>
  <media-slider-track>
    <media-slider-fill></media-slider-fill>
  </media-slider-track>
  <media-slider-thumb></media-slider-thumb>
  <media-slider-preview>
    <media-slider-value type="pointer"></media-slider-value>
  </media-slider-preview>
</media-volume-slider>
```

## Behavior

Controls the media volume level. The slider maps its 0–100 internal range to the media’s 0–1 volume scale. When the media is muted, the fill level drops to 0 regardless of the stored volume value.

When `volumeAvailability` is not `"available"`, the element receives native `hidden`, `data-hidden`, and disabled ARIA semantics. This is independent of `mutedAvailability`, which controls whether the mute button is shown.

## Styling

Use [CSS custom properties](#css-custom-properties) to style the fill and pointer levels:

```css
media-volume-slider::before {
  width: var(--media-slider-fill);
}
```

## Accessibility

Renders with `role="slider"` and an automatic `aria-label` that resolves to “Volume” from the active locale. Override with the `label` prop. Keyboard controls:

- Arrow Left / Arrow Right: step by `step` increment
- Page Up / Page Down: step by `largeStep` increment
- Home: set volume to 0
- End: set volume to max

Scroll wheel support:

- **Mouse wheel / trackpad scroll**: adjusts volume by `wheelStep` increment (default `5`)

## Examples

Nest sub-components for full control over the slider’s DOM structure. This example includes a track, fill bar, draggable thumb, and a tooltip that shows the volume percentage on hover.

**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-mute-button class="media-mute-button">
      <span class="muted">Unmute</span>
      <span class="unmuted">Mute</span>
    </media-mute-button>
    <media-volume-slider class="media-volume-slider">
      <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>
      <media-slider-value type="pointer" class="media-slider-value"></media-slider-value>
    </media-volume-slider>
  </media-container>
</video-player>
```

**index.css**

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

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

.media-mute-button {
  position: absolute;
  bottom: 10px;
  left: 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-mute-button .muted {
  display: none;
}
.media-mute-button .unmuted {
  display: none;
}
.media-mute-button[data-muted] .muted {
  display: inline;
}
.media-mute-button:not([data-muted]) .unmuted {
  display: inline;
}

.media-volume-slider {
  position: absolute;
  right: 10px;
  bottom: 10px;
  display: flex;
  align-items: center;
  width: 100px;
  height: 20px;
  cursor: pointer;
}

.media-slider-track {
  position: absolute;
  right: 0;
  left: 0;
  height: 4px;
  background: rgba(255, 255, 255, 0.3);
  border-radius: 9999px;
  backdrop-filter: blur(10px);
  transition: height 150ms ease;
}

.media-volume-slider[data-interactive] .media-slider-track {
  height: 6px;
}

.media-slider-fill {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--media-slider-fill);
  height: 100%;
  background: white;
  border-radius: 9999px;
}

.media-slider-thumb {
  position: absolute;
  left: var(--media-slider-fill);
  width: 14px;
  height: 14px;
  background: white;
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  transform: translateX(-50%) scale(0);
  transition: transform 150ms ease;
}

.media-volume-slider[data-interactive] .media-slider-thumb {
  transform: translateX(-50%) scale(1);
}

.media-volume-slider[data-dragging] .media-slider-thumb {
  transform: translateX(-50%) scale(1.1);
}

.media-slider-value {
  position: absolute;
  bottom: 100%;
  left: var(--media-slider-pointer);
  padding: 2px 6px;
  margin-bottom: 6px;
  font-size: 12px;
  color: white;
  white-space: nowrap;
  pointer-events: none;
  background: rgba(0, 0, 0, 0.8);
  border-radius: 4px;
  opacity: 0;
  transform: translateX(-50%);
  transition: opacity 150ms ease;
}

.media-volume-slider[data-pointing] .media-slider-value {
  opacity: 1;
}
```

**index.ts**

```ts
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/mute-button';
import '@videojs/html/ui/volume-slider';
import '@videojs/html/ui/slider-track';
import '@videojs/html/ui/slider-fill';
import '@videojs/html/ui/slider-thumb';
import '@videojs/html/ui/slider-value';
```

## API Reference

### media-volume-slider

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | — | Whether the slider is non-interactive. |
| `label` | `{ key: string; text: string } \| string \| ((state: SliderState) => Text \| string)` | `''` | Custom label for the slider. |
| `largeStep` (attribute `large-step`) | `number` | — | Large step increment (Page Up/Down keys). |
| `orientation` | `'horizontal' \| 'vertical'` | — | Axis of slider movement. |
| `step` | `number` | `DEFAULT_VOLUME_STEP` | Step increment for value changes (arrow keys). |
| `thumbAlignment` (attribute `thumb-alignment`) | `'center' \| 'edge'` | — | How the thumb aligns at the track edges. `edge` constrains the thumb within track bounds. |
| `wheelStep` (attribute `wheel-step`) | `number` | `DEFAULT_VOLUME_STEP` | Step increment for wheel scrolling. |

#### State

State is reflected as data attributes for CSS styling.

| Property | Type | Description |
| --- | --- | --- |
| `value` | `number` | Current slider value in the min–max range. |
| `fillPercent` | `number` | Fill level as a percentage (0–100), derived from value. |
| `pointerPercent` | `number` | Pointer position as a percentage of the track (0–100). |
| `dragging` | `boolean` | Whether the user is actively dragging. |
| `pointing` | `boolean` | Whether the pointer is over the slider. |
| `interactive` | `boolean` | Whether dragging, pointing, or focus is active. |
| `orientation` | `'horizontal' \| 'vertical'` | Axis of slider movement. |
| `disabled` | `boolean` | Whether the slider is non-interactive. |
| `thumbAlignment` | `'center' \| 'edge'` | How the thumb aligns at the track edges. |
| `volume` | `number` | Volume level from 0 (silent) to 1 (max). |
| `muted` | `boolean` | Whether audio is muted. |
| `availability` | `'available' \| 'unavailable' \| 'unsupported'` | |
| `hidden` | `boolean` | |

#### Data attributes

| Attribute | Type |
| --- | --- |
| `data-availability` | `'available' \| 'unavailable' \| 'unsupported'` |
| `data-hidden` | — |

#### CSS custom properties

| Variable | Description |
| --- | --- |
| `--media-slider-fill` | Fill level percentage (0–100), representing the current volume level. |
| `--media-slider-pointer` | Pointer position percentage (0–100), tracking the cursor along the slider. |

#### Events

| Event | Description |
| --- | --- |
| `drag-end` | Fired when a pointer drag ends. |
| `drag-start` | Fired when a pointer drag starts. |

### media-slider-fill

Displays the filled portion from start to the current value.

### media-slider-preview

Positioning container for preview content that tracks the pointer along the slider.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `overflow` | `'clamp' \| 'visible'` | — | How the preview handles slider boundaries. `clamp` keeps it within bounds; `visible` lets it extend past them. |

#### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-dragging` | — | Present when the user is actively dragging. |
| `data-pointing` | — | Present when the pointer is over the slider. |
| `data-interactive` | — | Present when dragging, pointing, or focus is active. |
| `data-orientation` | `'horizontal' \| 'vertical'` | Current axis of slider movement (`horizontal` or `vertical`). |
| `data-disabled` | — | Present when the slider is non-interactive. |

### media-slider-thumb

Draggable handle for setting the slider value. Receives focus and handles keyboard interaction.

#### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-dragging` | — | Present when the user is actively dragging. |
| `data-pointing` | — | Present when the pointer is over the slider. |
| `data-interactive` | — | Present when dragging, pointing, or focus is active. |
| `data-orientation` | `'horizontal' \| 'vertical'` | Current axis of slider movement (`horizontal` or `vertical`). |
| `data-disabled` | — | Present when the slider is non-interactive. |

### media-slider-track

Contains the slider's visual track and interactive hit zone.

### media-slider-value

Writes the formatted current or pointer slider value into its own text content, replacing any children.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | `'current' \| 'pointer'` | — | Which slider value to display: the current position or the pointer position. |

#### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-dragging` | — | Present when the user is actively dragging. |
| `data-pointing` | — | Present when the pointer is over the slider. |
| `data-interactive` | — | Present when dragging, pointing, or focus is active. |
| `data-orientation` | `'horizontal' \| 'vertical'` | Current axis of slider movement (`horizontal` or `vertical`). |
| `data-disabled` | — | Present when the slider is non-interactive. |

---

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