VideoPlayer
StablePlays a generated clip and lets you act on the exact frame you paused on.
Also known as video, media player
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
Generated take with frame actions
Take 3, harbour at dawn
8 seconds, 24 frames a second
[Gulls and soft waves]
Narrator: The first boat leaves just before sunrise.
Shows a small sailing boat crossing a calm harbour at dawn, from left to right, past a striped lighthouse.
These work while focus is inside the player, and never while typing.
- SpaceK Play or pause
- ←→ Back or forward 1 second
- ,. or Shift with an arrow: one frame
- HomeEnd Start or end
- M Mute
- C Captions
<VideoPlayer
label="Take 3, harbour at dawn"
subtitle="8 seconds, 24 frames a second"
duration={8}
fps={24}
aiGenerated
provenance={{
madeWith: "Video model, draft quality",
date: "3 October 2026",
editedByPerson: false,
actions: ["Made from your prompt and one start frame"],
why: "The file carries an invisible watermark and Content Credentials. Both can be lost if the clip is re-uploaded or re-encoded.",
}}
cues={cues}
transcript={transcript}
frameActions={[
{ id: "start", label: "Use as start frame" },
{ id: "extend", label: "Extend from here" },
{ id: "save", label: "Save frame" },
]}
onFrameAction={(id, seconds, frame) => useFrame(id, seconds, frame)}
/>
// With a real file, the length comes from the file:
<VideoPlayer label="Take 3, harbour at dawn" src="/takes/3.mp4" poster="/takes/3.jpg" fps={24}
captions={[{ src: "/takes/3.vtt", lang: "en", label: "English" }]} />No file is needed: the player draws a stand-in scene and simulates time. Press Play, pause anywhere, then step frames with the buttons, or with comma and period while focus is in the player. The paused-frame actions return the time and frame number.
When to use it
Lets someone watch a generated take, stop on the exact frame they want and use it, with every state in words.
Made of: Title · AI label or provenance · State or subtitle · Frame (16:9, 9:16 or 1:1) · Working edge while generating · Captions · Scrubber · Play · Frame back and forward · Time and frame · Loop · Mute · Captions toggle · Full screen · Paused-frame actions · Transcript · Keyboard shortcuts
Use it for
- Reviewing a take the AI made, then using a frame as the start of the next shot.
- Picking the point to extend or edit from.
- Showing a clip with captions and a transcript as its text alternative.
Not for
- A still image the AI made: use MediaFrame.
- Audio only, such as a call or a voice: use AudioPlayer.
- Choosing a stretch of the clip to edit or extend: use ClipRangeSelector.
- Progress of a long job with steps: use GenerationProgress.
Do and avoid
Take 2, harbour at dawn
Could not play
This clip could not be loaded. Credits for this take were returned.
These work while focus is inside the player, and never while typing.
- SpaceK Play or pause
- ←→ Back or forward 1 second
- HomeEnd Start or end
- M Mute
Take 2, harbour at dawn
Could not play
This clip could not be loaded. Your prompt broke the rules.
These work while focus is inside the player, and never while typing.
- SpaceK Play or pause
- ←→ Back or forward 1 second
- HomeEnd Start or end
- M Mute
Do
- Wait for a press before playing anything.
- Offer frame actions that match the next step, such as Use as start frame or Extend from here.
- Label every AI clip and say what the file carries.
- Give a transcript or captions for clips with speech.
Avoid
- Don't autoplay, or play on hover under reduced motion.
- Don't colour the player, the scrubber or the progress with the AI colour. Only the label and the working edge use it.
- Don't show a countdown or a made-up percentage while generating.
- Don't use red or blame words for a blocked take. Say what was blocked and whether credits came back.
On a phone
- The Play button is 44px; every other control grows to 44px on touch screens. The controls wrap on narrow screens.
- The scrubber's hit area is 44px tall even though the track is 4px.
- Tap the frame to play or pause. Double-tap its left or right side to go back or forward 2 seconds.
- Paused-frame actions sit in a row under the controls and wrap to full width, with 44px buttons.
- A 9:16 clip is capped in width so it does not push the controls off a phone screen.
Seen in products
- Google Flow: Shift with an arrow pauses and steps one frame.
- Sora's Loop editor had two handles for the start and end of the loop.
- The best players let you pause on a frame and save it, extend from there or edit from there.
- Flow labels every output with an invisible SynthID watermark; the visible watermark is a toggle.
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| labelrequired | string | none | Names the player and its scrubber, such as "Take 3, harbour at dawn". |
| subtitle | string | none | Second line. Without it the line shows the state in words. |
| src | string | none | A real video file. Leave out to simulate playback with a drawn scene. |
| duration | number | none | Length in seconds. Needed without src. |
| scene | "harbour" | "kite" | "harbour" | The drawn scene used without src. |
| poster | string | none | Still image shown before playing and while generating. |
| ratio | "16:9" | "9:16" | "1:1" | "16:9" | Shape of the frame. The space is reserved before anything loads. |
| fps | number | none | Frames per second. Turns on frame step buttons and keys, and frame numbers. |
| state | "generating" | "loading" | "error" | none | Forces a state the player cannot see. Ready, playing, paused, buffering and ended are tracked by the player. |
| statusNote | string | none | Words beside generating or error, such as the queue reason or whether credits came back. |
| onRetry | () => void | none | Shows Try again in the error state. |
| loop | boolean | false | Starts with loop on. |
| muted | boolean | false | Starts muted. |
| hasAudio | boolean | true | Set false when the clip has no sound. The mute button then says so. |
| captions | { src: string; lang: string; label: string }[] | none | Caption files for a real src, as native tracks. |
| cues | VideoCue[] | none | Caption lines drawn over the frame, with or without src. |
| transcript | ReactNode | none | Spoken words and a description of the clip, in a collapsed section. |
| frameActions | VideoFrameAction[] | none | Actions on the paused frame, such as "Use as start frame". Shown only while paused. |
| onFrameAction | (id: string, seconds: number, frame?: number) => void | none | Called with the action id, the time and the frame number. |
| shortcuts | boolean | true | Single-key shortcuts on at first. The person can turn them off inside the player. |
| aiGenerated | boolean | false | Adds an AI label beside the title. |
| aiNote | ReactNode | none | What the AI label explains, used when there is no provenance. |
| provenance | ProvenancePopoverProps | none | Opens model, date and what the file carries from the label. |
| current | number | none | Position in seconds when the parent keeps it. A change of more than a frame seeks. |
| onTimeChange | (seconds: number) => void | none | Called as the position moves. |
| onPlayingChange | (playing: boolean) => void | none | Called when playback starts or stops. |
| className | string | none | Extra classes on the player. |
"Show code under the preview gives the code for the example on the stage."
In Figma
In Figma, one VideoPlayer switches all of this from its properties panel, across 36 variants. Each property sets the React prop beside it, so design and code use the same names.
| Figma property | React | Values |
|---|---|---|
| Scene | scene | Harbour, Kite |
| Ratio | ratio | 16:9, 9:16, 1:1 |
| Status | state | Generating, Loading, Error |
| Loop | loop | On or off |
| Label | label | Any text |
States
States this part has beyond its default look. Build each one your screen can reach. States set by a property are in the Code tab.
- generating
- state="generating": first frame or poster dimmed under a working edge; nothing can play.
- loading
- state="loading", or while a src loads: a spinner and disabled controls.
- paused on a frame
- Pausing shows the frame actions with the time and frame number.
- error
- state="error" or a file that fails: a calm message, statusNote and Try again.
- one at a time
- Starting any video or audio player pauses the others on the page.
Tokens
Change one of these and every part that uses it follows.
--accent--fg--fg-muted--border--surface--surface-sunken--danger-fg--lime--chart-seq-1--chart-seq-5--chart-2--chart-4--dur-fast--dur-instant
Built in
- The player is a region named by its title.
- Play is a toggle button named Play with aria-pressed. When it cannot play it uses aria-disabled and stays focusable.
- The scrubber is a slider with the time in words, such as "3.2 seconds of 8 seconds, frame 77 of 192".
- The frame step buttons say the frame they go to, such as "Forward one frame, to frame 78".
- Loop, Mute and Captions are toggle buttons with aria-pressed. A clip with no sound says "No sound in this clip".
- Space plays or pauses while focus is in the player, except on a button. Arrows seek 1 second, Shift with an arrow steps a frame, Home and End jump to the ends.
- K, M, C, comma and period work only while focus is in the player and never in a text field. A switch in Keyboard shortcuts turns them off.
- The paused-frame actions are a named group that says the frame number.
- Ready after generating, ended, buffering and error are announced once. The clock and play or pause are not.
- Captions use native tracks for files, and the transcript sits in a collapsible section.
- The big play mark on the frame is for pointers only and is hidden from screen readers; the Play button is the real control.
What you need to do
- Give a label that tells takes apart, such as "Take 3, harbour at dawn". It names the player and its scrubber.
- Give a transcript, or cues for captions, for any clip with speech. Describe what the clip shows there too: the prompt is not a description.
- Never start playback from code. There is no autoplay prop on purpose.
- Say what the file carries in provenance (watermark, Content Credentials) and what can strip it. Do not promise more.
- Pass statusNote for a failed or blocked take that says whether credits came back.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide