# VolumeSlider

A slider component for controlling media playback volume

## Import

```tsx
import { VolumeSlider } from '@videojs/react';
```

## Anatomy

```tsx
<VolumeSlider.Root>
  <VolumeSlider.Track>
    <VolumeSlider.Fill />
  </VolumeSlider.Track>
  <VolumeSlider.Thumb />
  <VolumeSlider.Preview>
    <VolumeSlider.Value type="pointer" />
  </VolumeSlider.Preview>
</VolumeSlider.Root>
```

## 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 component returns `null`. 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:

React renders a `<div>` element. Add a `className` to style it:

```css
.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.

**App.tsx**

```tsx
import { Container, createPlayer, MuteButton, VolumeSlider } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';

const { Player } = createPlayer({ features: videoFeatures });

export default function WithParts() {
  return (
    <Player>
      <Container className="media-container">
        <Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop />
        <MuteButton
          className="media-mute-button"
          render={(props, state) => <button {...props}>{state.muted ? 'Unmute' : 'Mute'}</button>}
        />
        <VolumeSlider.Root className="media-volume-slider">
          <VolumeSlider.Track className="media-slider-track">
            <VolumeSlider.Fill className="media-slider-fill" />
          </VolumeSlider.Track>
          <VolumeSlider.Thumb className="media-slider-thumb" />
          <VolumeSlider.Value type="pointer" className="media-slider-value" />
        </VolumeSlider.Root>
      </Container>
    </Player>
  );
}
```

**App.css**

```css
.media-container {
  position: relative;
}

.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-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;
}
```

## API Reference

### Root

#### 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` | `number` | — | Large step increment (Page Up/Down keys). |
| `max` | `number` | — | |
| `min` | `number` | — | |
| `orientation` | `'horizontal' \| 'vertical'` | — | Axis of slider movement. |
| `step` | `number` | `DEFAULT_VOLUME_STEP` | Step increment for value changes (arrow keys). |
| `thumbAlignment` | `'center' \| 'edge'` | — | How the thumb aligns at the track edges. `edge` constrains the thumb within track bounds. |
| `value` | `number` | — | |
| `wheelStep` | `number` | `DEFAULT_VOLUME_STEP` | Step increment for wheel scrolling. |

#### State

State is accessible via the `render`, `className`, and `style` props.

| 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. |

### Fill

Displays the filled portion from start to the current value.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: SliderState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `render` | `ReactElement \| ((props: HTMLProps, state: SliderState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: SliderState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

### Preview

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

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: SliderState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `overflow` | `'clamp' \| 'visible'` | — | How the preview handles slider boundaries. `clamp` keeps it within bounds; `visible` lets it extend past them. |
| `render` | `ReactElement \| ((props: HTMLProps, state: SliderState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: SliderState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

#### 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. |

### Thumb

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

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: SliderState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `render` | `ReactElement \| ((props: HTMLProps, state: SliderState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: SliderState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

#### 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. |

### Track

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

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: SliderState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `render` | `ReactElement \| ((props: HTMLProps, state: SliderState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: SliderState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

### Value

Displays a formatted text representation of the slider value. Renders an `<output>` element.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: SliderState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `format` | `((value: number) => string)` | — | Custom formatter for the displayed value. Overrides the root's `formatValue`. |
| `render` | `ReactElement \| ((props: HTMLProps, state: SliderState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: SliderState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |
| `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. |

---

React documentation: https://videojs.org/docs/framework/react/llms.txt
All documentation: https://videojs.org/llms.txt
