# Video Controls

Player controls over any video, with a scrubber, play and pause, skip, speed, captions, mute and fullscreen. Double-tap either side to seek, plus a minimal short-form mode.

> Composed · Pro · Mobile-Native — React Native, Expo Go compatible.

## Install

```bash
npx @lumioui/cli add pro/video-controls
```

## Import

```tsx
import { formatPlaybackTime, VideoControls } from "@/components/ui/video-controls/video-controls";
```

## Variants

- `course` — Course lesson: `npx @lumioui/cli add pro/video-controls-course`
- `reels` — Short-form minimal: `npx @lumioui/cli add pro/video-controls-reels`
- `streaming` — Streaming: `npx @lumioui/cli add pro/video-controls-streaming`

## Props

### VideoScrubber

| Prop | Type | Notes |
| --- | --- | --- |
| `position` | `number` | Current playback position in seconds. |
| `duration` | `number` | Total length in seconds. |
| `buffered?` | `number` | Buffered-ahead position in seconds — drawn as a lighter range. |
| `playing?` | `boolean` | While true the playhead advances between position updates. |
| `rate?` | `number` | Playback rate — scales the in-between advance. Default 1. |
| `updateInterval?` | `number` | How often your player reports position, in ms. Default 500. |
| `onScrubStart?` | `() => void` | Fires once when a scrub starts. |
| `onScrub?` | `(seconds: number) => void` | Fires while scrubbing, once per whole second crossed. |
| `onSeek?` | `(seconds: number) => void` | Fires on release with the seek target in seconds. |
| `thin?` | `boolean` | Hairline track with no resting thumb (minimal / short-form). Default false. |
| `showBubble?` | `boolean` | Show the time bubble above the thumb while scrubbing. Default true. |
| `disabled?` | `boolean` | — |
| `className?` | `string` | — |

### VideoControls

| Prop | Type | Notes |
| --- | --- | --- |
| `playing?` | `boolean` | Controlled play state. |
| `defaultPlaying?` | `boolean` | Uncontrolled initial play state. Default false. |
| `onPlayingChange?` | `(playing: boolean) => void` | Fires with the next play state — call play()/pause() here. |
| `position?` | `number` | Current position in seconds. |
| `duration` | `number` | Total length in seconds. |
| `buffered?` | `number` | Buffered-ahead position in seconds. |
| `onSeek?` | `(seconds: number) => void` | Seek request in seconds — from the scrubber, skip buttons and double-tap. |
| `onScrub?` | `(seconds: number) => void` | Live scrub position, once per whole second (e.g. thumbnail previews). |
| `skipInterval?` | `number` | Skip distance for the ± buttons and double-tap. Default 10. |
| `rate?` | `number` | Current playback rate. Shows the speed pill when `onRateChange` is set. |
| `rates?` | `number[]` | Rates the speed pill cycles through. Default [1, 1.25, 1.5, 2, 0.75]. |
| `onRateChange?` | `(rate: number) => void` | — |
| `captions?` | `boolean` | Captions state. The CC button shows when `onCaptionsChange` is set. |
| `onCaptionsChange?` | `(on: boolean) => void` | — |
| `muted?` | `boolean` | Mute state. The speaker button shows when `onMutedChange` is set. |
| `onMutedChange?` | `(muted: boolean) => void` | — |
| `fullscreen?` | `boolean` | Fullscreen state. The expand button shows when `onFullscreenChange` is set. |
| `onFullscreenChange?` | `(fullscreen: boolean) => void` | — |
| `title?` | `string` | Top-bar title (full variant). |
| `subtitle?` | `string` | Top-bar second line (full variant). |
| `onClose?` | `() => void` | Renders a collapse chevron top-left (full variant). |
| `variant?` | `"full" | "minimal"` | `full` (streaming-player chrome) or `minimal` (short-video feed). Default `full`. |
| `visible?` | `boolean` | Controlled chrome visibility (full variant). |
| `defaultVisible?` | `boolean` | Uncontrolled initial chrome visibility. Default true. |
| `onVisibleChange?` | `(visible: boolean) => void` | — |
| `autoHide?` | `number` | Hide the chrome after this many ms of playing without interaction. 0 disables. Default 3000. |
| `updateInterval?` | `number` | How often your player reports position, in ms — the playhead interpolates between. Default 500. |
| `aspectRatio?` | `number | null` | Width / height of the player. Default 16/9. Pass `null` to fill the parent. |
| `children?` | `React.ReactNode` | The video surface, rendered full-bleed under the chrome. |
| `className?` | `string` | — |

## Icons rendered

- `play-pause` — /icons.html?i=play-pause
- `chevron-down` — /icons.html?i=chevron-down

Source: https://lumioui.com/components/video-controls