---
title: "Subtitle"
canonical_url: https://docs.zvid.io/docs/structure/subtitle/
source: docs/structure/subtitle.md
content_revision: 6338386a1dd87e4a
---

# Subtitle

The root-level `subtitle` property burns word-timed captions into the video.
Adding captions can be as simple as:

```json
{
  "subtitle": {
    "captions": [{ "start": 0, "end": 2, "text": "Hello world" }]
  }
}
```

Word timings are generated automatically from the text (proportionally to
word length), so every animation works out of the box. You can still provide
exact per-word timings when you have them (e.g. from a transcription model).

## Interface

```typescript
interface Subtitle {
  /** content — provide exactly ONE of src / captions */
  src?: string; // public http(s) URL of an SRT or VTT file
  captions?: Caption[];

  /** animation */
  animation?: SubtitleAnimation; // default "normal"
  direction?: "up" | "down" | "left" | "right"; // slide only, default "up"
  activeWord?: { color?: string; background?: string; radius?: number };

  /** typography */
  font?: {
    family?: string; // Google Fonts name, default "Poppins"
    size?: number; // px, default 50
    color?: string; // hex (+ optional alpha), default "#FFFFFF"
    bold?: boolean;
    italic?: boolean;
    transform?: "uppercase" | "lowercase" | "capitalize";
  };
  stroke?: { color: string; width: number };

  /** background box */
  background?: {
    color?: string; // hex (+ optional alpha)
    opacity?: number; // 0–1, multiplies the color's alpha
    padding?: number; // px around the text
    radius?: number; // corner radius in px, 0–200, default 0
  };

  /** placement */
  position?: // default "bottom"
  | "top"
    | "center"
    | "bottom" // shorthands for *-center
    | "top-left"
    | "top-center"
    | "top-right"
    | "center-left"
    | "center-center"
    | "center-right"
    | "bottom-left"
    | "bottom-center"
    | "bottom-right";
  margin?: { x?: number; y?: number }; // px from edge, default 5% of size

  /** layout */
  maxWordsPerLine?: number; // split captions into lines of ≤ N words
}

interface Caption {
  start: number; // seconds
  end: number; // seconds
  text?: string; // words auto-timed when `words` is omitted
  words?: Word[]; // optional exact per-word timing
}
```

Each caption needs `text` and/or `words`. See [`Caption`](https://docs.zvid.io/docs/structure/properties/caption/)
and [`Word`](https://docs.zvid.io/docs/structure/properties/word/).

Caption and word timestamps are **absolute seconds on the project timeline**,
not offsets from the caption start. Subtitles are project-level, including
when visuals use scenes. Use increasing `start`/`end` values and keep each
word inside its caption's interval. `maxWordsPerLine` is an integer from 1
to 20; omitted, no word-count limit is imposed by this field.

## Loading captions from a file (`src`)

Instead of inlining `captions`, point `src` at a public SRT or VTT file. The
file is fetched at render time, parsed, and word timings are distributed
automatically:

```json
{
  "subtitle": {
    "src": "https://cdn.example.com/captions.srt",
    "animation": "highlight",
    "activeWord": { "color": "#0b0d12", "background": "#7CFFB2" }
  }
}
```

`src` and `captions` are mutually exclusive. The URL must be a public
http(s) address (private hosts and non-standard ports are rejected).

## Animations

| `animation`   | Behavior                                                             |
| ------------- | -------------------------------------------------------------------- |
| `normal`      | Static captions (default). `none` is an alias.                       |
| `one-word`    | Only the word being spoken is shown.                                 |
| `karaoke`     | Full text; the spoken word switches to `activeWord.color`.           |
| `highlight`   | Karaoke plus a box behind the spoken word (`activeWord.background`). |
| `progressive` | Words appear as they are spoken and stay.                            |
| `fill`        | Color sweeps across each word while it is spoken (true karaoke).     |
| `pop`         | The spoken word scales up with a punchy two-stage animation.         |
| `bounce`      | The spoken word bounces in with a spring overshoot.                  |
| `fade`        | Words fade in as they are spoken.                                    |
| `typewriter`  | Characters type on at the spoken pace.                               |
| `slide`       | Each word slides into its slot (`direction`: up/down/left/right).    |

There are 11 distinct modes; `none` is an alias for `normal`.

### Mode Gallery

The same caption rendered in every animation mode — hover to play:


Effect previews (each link demonstrates the named effect):

- [`bounce` preview](https://cdn.zvid.io/library/docs/subtitle-mode-bounce.mp4)
- [`fade` preview](https://cdn.zvid.io/library/docs/subtitle-mode-fade.mp4)
- [`fill` preview](https://cdn.zvid.io/library/docs/subtitle-mode-fill.mp4)
- [`highlight` preview](https://cdn.zvid.io/library/docs/subtitle-mode-highlight.mp4)
- [`karaoke` preview](https://cdn.zvid.io/library/docs/subtitle-mode-karaoke.mp4)
- [`normal` preview](https://cdn.zvid.io/library/docs/subtitle-mode-normal.mp4)
- [`one-word` preview](https://cdn.zvid.io/library/docs/subtitle-mode-one-word.mp4)
- [`pop` preview](https://cdn.zvid.io/library/docs/subtitle-mode-pop.mp4)
- [`progressive` preview](https://cdn.zvid.io/library/docs/subtitle-mode-progressive.mp4)
- [`slide` preview](https://cdn.zvid.io/library/docs/subtitle-mode-slide.mp4)
- [`typewriter` preview](https://cdn.zvid.io/library/docs/subtitle-mode-typewriter.mp4)


## Examples

### Karaoke captions with a background box

```json
{
  "subtitle": {
    "captions": [
      { "start": 0, "end": 3, "text": "Let's create amazing videos" }
    ],
    "animation": "karaoke",
    "activeWord": { "color": "#FFD700" },
    "font": { "family": "Montserrat", "size": 50, "bold": true },
    "background": { "color": "#000000", "opacity": 0.8, "padding": 12 },
    "position": "center"
  }
}
```

### Short lines, top of frame

```json
{
  "subtitle": {
    "captions": [
      {
        "start": 0,
        "end": 5,
        "text": "This caption is split into short lines automatically"
      }
    ],
    "maxWordsPerLine": 4,
    "position": "top",
    "margin": { "x": 40, "y": 60 },
    "font": { "size": 40, "transform": "uppercase" },
    "stroke": { "color": "#000000", "width": 3 }
  }
}
```

### Exact word timings (e.g. from Whisper)

```json
{
  "subtitle": {
    "captions": [
      {
        "start": 0.5,
        "end": 2,
        "text": "Welcome to our video",
        "words": [
          { "start": 0.5, "end": 0.9, "text": "Welcome" },
          { "start": 0.9, "end": 1.1, "text": "to" },
          { "start": 1.1, "end": 1.4, "text": "our" },
          { "start": 1.4, "end": 2, "text": "video" }
        ]
      }
    ],
    "animation": "fill",
    "activeWord": { "color": "#7CFFB2" }
  }
}
```

## Limits and notes

- `stroke` and `background` can be combined — the text keeps its outline on
  top of the caption box.
- Captions are counted against your plan's caption limit; `src` files are
  capped at 5,000 cues and 2 MB.
- Set `background.radius` for rounded caption boxes and `activeWord.radius`
  for rounded active-word boxes. Both use pixels, accept 0–200, and default
  to square corners (`0`). A radius needs a corresponding background color
  to be visible. Active-word boxes apply to modes that display them.
- Subtitles are not available on image renders.

## Legacy schema

The previous shape — `{ "captions": [...], "styles": { "mode": ..., "isBold": ..., "marginV": ... } }` —
remains accepted and uses the same rendering pipeline; see
[`SubtitleStyles`](https://docs.zvid.io/docs/structure/properties/subtitle-styles/). It cannot be mixed with
the flat style fields above in the same subtitle object.

## Related Pages

- [JSON Structure](https://docs.zvid.io/docs/structure/)
- [Caption](https://docs.zvid.io/docs/structure/properties/caption/)
- [Word](https://docs.zvid.io/docs/structure/properties/word/)
- [SubtitleStyles (legacy)](https://docs.zvid.io/docs/structure/properties/subtitle-styles/)
