---
title: HTML Schema Reference
description: "Complete reference for authoring Hyperframes HTML compositions."
---

This is the full schema reference for Hyperframes compositions. For a gentler introduction, see [Compositions](/concepts/compositions) and [Data Attributes](/concepts/data-attributes).

## Overview

Hyperframes uses HTML as the source of truth for describing a video:

- **HTML clips** = video, image, audio, composition
- **[Data attributes](/concepts/data-attributes)** = timing, metadata, styling
- **CSS** = positioning and appearance
- **GSAP timeline** = animations and playback sync (see [GSAP Animation](/guides/gsap-animation))

## Framework-Managed Behavior

The framework reads data attributes and automatically manages:

- **Primitive clip timeline entries** — reads `data-start`, `data-duration`, and `data-track-index` from clips and adds them to the GSAP timeline
- **Media playback** (play, pause, seek) for `<video>` and `<audio>`
- **Clip lifecycle** — clips are mounted/unmounted based on `data-start` and `data-duration`
- **Timeline synchronization** — keeps media in sync with the GSAP master timeline
- **Media loading** — waits for all media to load before resolving timing

Mounting/unmounting controls **presence**, not appearance. Transitions (fade in, slide in) are animated in scripts.

<Warning>
  Do not manually call `video.play()`, `video.pause()`, set `audio.currentTime`, or mount/unmount clips in scripts. The framework owns media playback and clip lifecycle. See [Common Mistakes](/guides/common-mistakes) for more details.
</Warning>

## Viewport

Every composition must include `data-width` and `data-height` on the root element:

```html
<div id="main" data-composition-id="my-video"
     data-start="0" data-width="1920" data-height="1080">
  <!-- clips -->
</div>
```

Common sizes:
- **Landscape**: `data-width="1920" data-height="1080"`
- **Portrait**: `data-width="1080" data-height="1920"`

## All Clip Attributes

| Attribute | Applies To | Required | Description |
|-----------|-----------|----------|-------------|
| `id` | All | Yes | Unique identifier (e.g., `"el-1"`). Used for relative timing references and CSS targeting. |
| `class="clip"` | Visible elements | Yes | Enables runtime visibility management. Omit for audio-only clips. |
| `data-start` | All | Yes | Start time in seconds (e.g., `"0"`, `"5.5"`), or a clip ID reference for [relative timing](#relative-timing) (e.g., `"intro"`). |
| `data-duration` | video, img, audio, nested composition hosts | See below | Timeline slot duration in seconds. **Required** for images and nested composition hosts. Optional for video/audio (defaults to source duration). |
| `data-track-index` | All | Yes | Timeline track number. Controls z-ordering (higher = in front). Clips on the same track cannot overlap. |
| `data-media-start` | video, audio | No | Playback offset / trim point in source file (seconds). Default: `0`. See [Data Attributes](/concepts/data-attributes). |
| `data-playback-start` | video, audio, composition | No | Source-time offset in seconds. On a nested composition, this is the child timeline time shown at the host's `data-start`. Default: `0`. |
| `data-playback-rate` | video, audio, composition | No | Source playback multiplier clamped to `0.1`–`5`. Invalid values default to `1`. |
| `data-volume` | audio, video | No | Volume level from `0` to `1`. Default: `1`. |
| `data-composition-id` | div | On compositions | Unique composition ID. Must match the key used in `window.__timelines`. |
| `data-composition-src` | div | No | Path to external composition HTML file (for [nested compositions](#composition-clips)). |
| `data-variable-values` | div | No | JSON object of values passed to a nested composition. Read via `getVariables()` in scripts, or consumed automatically by declarative bindings. |
| `data-var-src` | img, video, audio | No | Binds the element's `src` to a declared variable id — the runtime substitutes the value (URL string or image `{url}`); the authored `src` is the fallback. |
| `data-var-text` | any | No | Binds the element's own text to a scalar variable id. Element children are preserved. |
| `data-color-grading` | img, video | No | Validated JSON payload for media-level correction, grading, LUT, finishing, and shader effects. Prefer Studio or `hyperframes media-treatment` to author it. |
| `data-width` | div | On compositions | Composition width in pixels. |
| `data-height` | div | On compositions | Composition height in pixels. |

## Clip Types

<AccordionGroup>
  <Accordion title="Video Clips">
    Video clips embed `<video>` elements with timing and playback attributes.

    ```html
    <video
      id="el-1"
      data-start="0"
      data-duration="15"
      data-track-index="0"
      data-media-start="0"
      src="./assets/video.mp4"
    ></video>
    ```

    **Key behavior:**
    - `data-duration` is **optional** — defaults to the remaining duration of the source file from `data-media-start`
    - If source media runs out before `data-duration`, the clip shows the last frame (freeze frame)
    - `data-media-start` trims the beginning of the source video — `data-media-start="5"` starts playback 5 seconds into the source file
    - `data-volume` controls the audio volume of the video — set to `"0"` for silent video
    - Do **not** add `class="clip"` to video elements — the framework manages their visibility directly

    <Warning>
      Do not animate `width`, `height`, `top`, or `left` directly on `<video>` elements with GSAP. This can cause Chrome to stop rendering video frames. Wrap the video in a `<div>` and animate the wrapper instead. See [Common Mistakes](/guides/common-mistakes).
    </Warning>
  </Accordion>

  <Accordion title="Image Clips">
    Image clips display static images with controlled timing.

    ```html
    <img
      id="el-2"
      class="clip"
      data-start="5"
      data-duration="4"
      data-track-index="1"
      src="./assets/overlay.png"
    />
    ```

    **Key behavior:**
    - `data-duration` is **required** for images (unlike video/audio, there is no source duration to default to)
    - `class="clip"` is **required** — this enables the runtime to show/hide the image based on timing
    - Supported formats: PNG, JPG, WebP, SVG, GIF. Animated GIFs are prepared as timeline-synced video for preview and render; `data-loop` can override GIF loop metadata.
    - Position and size with CSS — the image renders at its natural size unless styled otherwise
  </Accordion>

  <Accordion title="Audio Clips">
    Audio clips add sound to the composition without any visual element.

    ```html
    <audio
      id="el-4"
      data-start="0"
      data-duration="30"
      data-track-index="2"
      src="./assets/music.mp3"
    ></audio>
    ```

    **Key behavior:**
    - `data-duration` is **optional** — defaults to the remaining duration of the source file from `data-media-start`
    - Audio clips are invisible — do not add `class="clip"` (there is nothing to show/hide)
    - `data-volume` controls volume — use `"0.5"` for background music at 50% volume
    - `data-media-start` trims the beginning of the audio source, just like video
    - Multiple audio clips can overlap on different tracks for layered sound design
  </Accordion>

  <Accordion title="Composition Clips (Nested)">
    Composition clips embed one composition inside another, enabling modular, reusable video building blocks.

    ```html
    <div
      id="el-5"
      data-composition-id="intro-anim"
      data-composition-src="compositions/intro-anim.html"
      data-start="0"
      data-track-index="3"
    ></div>
    ```

    **Key behavior:**
    - Compositions do **not** use `data-duration` — duration is determined by the composition's GSAP timeline (`tl.duration()`)
    - External compositions are loaded from `data-composition-src` and wrapped in `<template>` tags
    - Each nested composition has its own `window.__timelines` entry, registered by its own `<script>` block
    - The framework automatically nests sub-timelines — do not manually add them to the parent timeline
    - Any composition can be nested inside any other — there is no special "root" type
    - Per-instance values can be passed with `data-variable-values`; sub-composition scripts read them via `getVariables()`, and `data-var-*` bindings / `var(--id)` CSS resolve them automatically

    For more on how compositions work, see [Compositions](/concepts/compositions).
  </Accordion>
</AccordionGroup>

## Media Treatments

Real `<img>` and `<video>` elements may carry a `data-color-grading` payload:

```html index.html
<video
  id="hero"
  src="assets/hero.mp4"
  data-start="0"
  data-track-index="0"
  muted
  playsinline
  data-color-grading='{
    "preset":"clean-studio",
    "intensity":0.8,
    "adjust":{"highlights":-0.08,"shadows":0.06},
    "effects":{"bloom":0.12},
    "colorSpace":"rec709"
  }'
></video>
```

The runtime renders the complete payload on a sibling WebGL canvas. It does not
apply to text, SVG, arbitrary DOM, or CSS background images. Use Studio or
[`media-treatment`](/packages/cli#media-treatment) for normal authoring; see
[Color Grading](/guides/color-grading) and [Media Effects](/guides/media-effects)
for the supported workflow and current SDR/HDR boundary.

## Relative Timing

Reference another clip's ID in `data-start` to mean "start when that clip ends":

```html
<video id="intro" data-start="0" data-duration="10" data-track-index="0" src="..."></video>
<video id="main" data-start="intro" data-duration="20" data-track-index="0" src="..."></video>
```

`main` starts at second 10 (when `intro` ends).

**Offsets** let you add gaps or overlaps:

```html
<!-- 2-second gap after intro -->
<video id="main" data-start="intro + 2" data-duration="20" data-track-index="0" src="..."></video>

<!-- 0.5-second overlap with intro -->
<video id="main" data-start="intro - 0.5" data-duration="20" data-track-index="0" src="..."></video>
```

For a deeper explanation, see the [relative timing section](/concepts/data-attributes#relative-timing) in the Data Attributes concept page.

## Timeline Contract

The framework initializes `window.__timelines = {}` before any scripts run. Every composition must register a GSAP timeline at the key matching its `data-composition-id`:

```javascript
const tl = gsap.timeline({ paused: true });

// Add animations
tl.to("#title", { opacity: 1, duration: 0.5 }, 0);
tl.to("#title", { opacity: 0, duration: 0.5 }, 4.5);

// Register the timeline
window.__timelines["<data-composition-id>"] = tl;
```

### Rules

- Every composition needs a `<script>` block that creates and registers its timeline
- All timelines must start paused (`{ paused: true }`)
- The framework auto-nests sub-timelines into the parent — do **not** manually add them
- Duration comes from `tl.duration()` — do **not** add `data-duration` on composition elements
- Timelines must be finite (no infinite loops or repeats)
- The timeline ID must exactly match the `data-composition-id` attribute on the root element

For a complete guide to working with GSAP timelines, see [GSAP Animation](/guides/gsap-animation).

## Caption Discoverability

For caption compositions, add these attributes to the root node so the framework can identify and special-case caption rendering:

```html
<div
  data-composition-id="captions"
  data-timeline-role="captions"
  data-caption-root="true"
  ...
>
```

## Output Checklist

<Check>
  Before rendering, verify your composition meets these requirements:

  - Every composition has `data-width` and `data-height` on the root element
  - Each reusable composition is in its own HTML file
  - External compositions are loaded via `data-composition-src`
  - Each external composition file uses a `<template>` wrapper
  - All GSAP timelines are registered in `window.__timelines` with the correct ID
  - Timed visible elements (images, divs) have `class="clip"`
  - Video elements do **not** have `class="clip"` (framework manages them directly)
  - All `data-start` references point to existing clip IDs
  - Run `npx hyperframes lint` to catch structural issues automatically
</Check>
