caption#
Word-timed captions with kinetic animation styles. The schema's flagship "AI captions" surface — pass an array of timestamped words (e.g., direct from Whisper) and a style name, and the runtime renders them with sub-word timing. Inherits common fields.
interface CaptionElement extends BaseElement { type: "tok-str">'caption'; words: CaptionWord[]; style?: "tok-str">'tiktok_bounce' | "tok-str">'fade_reveal' | "tok-str">'kinetic_typewriter' | "tok-str">'word_pop'; max_length?: number | "tok-str">'auto'; "tok-cmt">// windowing — see below "tok-cmt">// Text-like styling (same semantics as `text`) font_family?: string; font_size?: number | string; "tok-cmt">// px, a string, or "tok-str">'auto' to fit (default "tok-str">'auto') font_weight?: number | string; font_style?: "tok-str">'normal' | "tok-str">'italic'; fill_color?: string; stroke_color?: string; stroke_width?: number; text_align?: "tok-str">'left' | "tok-str">'center' | "tok-str">'right'; line_height?: number; letter_spacing?: number; background_color?: string; background_border_radius?: number; background_padding?: number | [number, number]; text_shadow?: TextShadow | TextShadow[]; "tok-cmt">// Caption-specific highlight_color?: string; highlight_background_color?: string; } interface TextShadow { color: string; offset_x?: number; "tok-cmt">// default 0 offset_y?: number; "tok-cmt">// default 0, positive = down blur?: number; "tok-cmt">// shadow blur sigma in px, default 0 opacity?: number; "tok-cmt">// 0..1, default 1 }
Words#
interface CaptionWord { text: string; start: number; end: number; }
| Field | Type | Description |
|---|---|---|
text | string | The word as it should render. |
start | number | When the word becomes active, in seconds relative to the caption element's time. |
end | number | When the word stops being active. |
Words don't have to be space-separated tokens — passing { text: "—" } between sentences renders a visible em-dash; punctuation can ride on the preceding word.
Windowing (max_length)#
A whole transcript on one caption would render as one unreadable block. max_length shows only part of it at a time — only the chunk active at the current moment displays (inside the box, wrapped):
| Value | Meaning |
|---|---|
number | Max letters per chunk — a chunk grows word-by-word until the next word would exceed this many characters (letter-budget chunking). |
'auto' | Chunk by a few words (also breaking on pauses) — the sensible default for speech. |
| absent | No windowing — the whole transcript shows at once. |
Windowing is a display rule: it doesn't change word start/end, and the kinetic style still animates within the active chunk. Transcribing in the editor sets max_length: 'auto' by default.
Style#
| Field | Type | Default | Description |
|---|---|---|---|
style | 'tiktok_bounce' | 'fade_reveal' | 'kinetic_typewriter' | 'word_pop' | 'tiktok_bounce' | Per-word animation behavior. |
tiktok_bounce#
The active word scales up ~18% and gets the highlight color; previous words stay visible at the base fill color. Designed for vertical social video.
fade_reveal#
Words fade in as they activate and stay visible. Calmer than tiktok_bounce. Default for documentary / explainer pacing.
kinetic_typewriter#
Words pop in one at a time, no overlap. Each word stays until the next activates, then is replaced. Use this when you want one word on screen at a time.
word_pop#
The active word scales briefly (~120ms) on activation, then settles. Previous words stay at the base style. Subtler than tiktok_bounce.
Highlight colors#
| Field | Type | Default | Description |
|---|---|---|---|
highlight_color | string (hex) | '#ffd60a' | Color applied to the currently-active (currently-spoken) word. |
highlight_background_color | string (hex) | — | Background plate color applied behind the currently-active word. Pair with background_border_radius for a chip effect. |
Pair highlight_color with a contrast fill_color to make the active word "pop" — for example, white words with a yellow highlight.
Typography#
Same fields as text. See that page for font_family, font_size (default 'auto' — fits the box), font_weight, font_style, text_align (default 'left'), line_height, letter_spacing, the background plate (background_color, background_border_radius, background_padding), and text_shadow.
text_shadow is an object — or an array of objects rendered back-to-front — of the form { color, offset_x, offset_y, blur, opacity }. offset_x / offset_y default 0, blur (sigma in px) defaults 0, and opacity (0..1) defaults 1. There are no flat shadow_color / shadow_x / shadow_y / shadow_blur fields.
Example: Whisper-style captions#
{ "type": "caption", "x": 540, "y": 1600, "x_anchor": "50%", "layer": 4, "time": 1, "duration": 8, "style": "tiktok_bounce", "font_family": "Inter", "font_size": 64, "font_weight": 800, "fill_color": "#ffffff", "highlight_color": "#facc15", "stroke_color": "#000000", "stroke_width": 4, "words": [ { "text": "this", "start": 0, "end": 0.35 }, { "text": "is", "start": 0.35, "end": 0.55 }, { "text": "how", "start": 0.55, "end": 0.80 }, { "text": "agents","start": 0.80, "end": 1.30 }, { "text": "make", "start": 1.30, "end": 1.60 }, { "text": "video", "start": 1.60, "end": 2.20 } ] }
Notes#
- Whisper integration — Whisper's word-level timestamps map directly onto the
wordsarray. The MCP server's authoring guide includes a one-shot recipe. - Caption duration vs word duration — the element's
duration(from common fields) bounds when the caption is on screen. Words withstart/endoutside the element's[0, duration]are clipped. - Animation interaction — caption styles handle per-word animation. Top-level
animationson the caption element fire on the whole element (e.g., afade-inat start applies before the per-word style takes over).