diff --git a/docs/packages/core.mdx b/docs/packages/core.mdx
index b7dbd543a..56c32b4cd 100644
--- a/docs/packages/core.mdx
+++ b/docs/packages/core.mdx
@@ -130,6 +130,18 @@ const meta: CompositionMetadata = extractCompositionMetadata(htmlString);
// data-composition-variables='[{"id":"title","label":"Title","type":"string","default":"Hello"}]'
// >
+// Read resolved variables inside a composition (declared defaults +
+// CLI overrides + per-instance host data-variable-values):
+import { getVariables } from '@hyperframes/core';
+const { title } = getVariables<{ title: string }>();
+
+// Validate CLI / host overrides against the declared schema:
+import { validateVariables, formatVariableValidationIssue } from '@hyperframes/core';
+const issues = validateVariables({ title: 'Hello', count: 'three' }, meta.variables);
+for (const issue of issues) {
+ console.warn(formatVariableValidationIssue(issue));
+}
+
// Generate HTML from structured data
const html = generateHyperframesHtml(elements, {
animations,
diff --git a/packages/cli/src/docs/compositions.md b/packages/cli/src/docs/compositions.md
index 6a0d42a9d..5af91e5c1 100644
--- a/packages/cli/src/docs/compositions.md
+++ b/packages/cli/src/docs/compositions.md
@@ -26,14 +26,33 @@ Use `npx hyperframes compositions` to see all compositions in a project.
## Variables
-HyperFrames does not automatically bind `data-var-*` attributes into your composition DOM.
+Declare variables on the composition's `` root, then read them inside any composition script with `window.__hyperframes.getVariables()`. Override per-instance via `data-variable-values` on the host element, or at render time via `npx hyperframes render --variables '{...}'`.
```html
-
+
+
+
+
+
+
+
+
+
```
-Read `data-variable-values` inside the nested composition and apply the values in your own script. Variable metadata for tooling is declared separately via `data-composition-variables` and read with `extractCompositionMetadata()`.
+```html
+
+
+
+```
+
+The runtime layers `data-variable-values` over the sub-comp's declared defaults on a per-instance basis. The same `getVariables()` call works at the top level too — the CLI flag `--variables` provides the override, declared `default`s fall through for missing keys.
diff --git a/skills/hyperframes-cli/SKILL.md b/skills/hyperframes-cli/SKILL.md
index 7fd73e16a..6e0749897 100644
--- a/skills/hyperframes-cli/SKILL.md
+++ b/skills/hyperframes-cli/SKILL.md
@@ -101,20 +101,25 @@ npx hyperframes render --format webm # transparent WebM
npx hyperframes render --docker # byte-identical
```
-| Flag | Options | Default | Notes |
-| -------------- | --------------------- | -------------------------- | --------------------------- |
-| `--output` | path | renders/name_timestamp.mp4 | Output path |
-| `--fps` | 24, 30, 60 | 30 | 60fps doubles render time |
-| `--quality` | draft, standard, high | standard | draft for iterating |
-| `--format` | mp4, webm | mp4 | WebM supports transparency |
-| `--workers` | 1-8 or auto | auto | Each spawns Chrome |
-| `--docker` | flag | off | Reproducible output |
-| `--gpu` | flag | off | GPU-accelerated encoding |
-| `--strict` | flag | off | Fail on lint errors |
-| `--strict-all` | flag | off | Fail on errors AND warnings |
+| Flag | Options | Default | Notes |
+| -------------------- | --------------------- | -------------------------- | ------------------------------------------------------------------ |
+| `--output` | path | renders/name_timestamp.mp4 | Output path |
+| `--fps` | 24, 30, 60 | 30 | 60fps doubles render time |
+| `--quality` | draft, standard, high | standard | draft for iterating |
+| `--format` | mp4, webm | mp4 | WebM supports transparency |
+| `--workers` | 1-8 or auto | auto | Each spawns Chrome |
+| `--docker` | flag | off | Reproducible output |
+| `--gpu` | flag | off | GPU-accelerated encoding |
+| `--strict` | flag | off | Fail on lint errors |
+| `--strict-all` | flag | off | Fail on errors AND warnings |
+| `--variables` | JSON object | — | Override variable values declared in `data-composition-variables` |
+| `--variables-file` | path | — | JSON file with variable values (alternative to `--variables`) |
+| `--strict-variables` | flag | off | Fail render on undeclared keys or type mismatches in `--variables` |
**Quality guidance:** `draft` while iterating, `standard` for review, `high` for final delivery.
+**Parametrized renders:** declare variables on `` and read them inside the composition with `window.__hyperframes.getVariables()`. Override at render time with `--variables '{"title":"Q4 Report"}'`. Missing keys fall through to declared defaults, so the same composition runs unchanged in dev preview and in production renders. See the `hyperframes` skill for the full pattern.
+
## Transcription
```bash
diff --git a/skills/hyperframes/SKILL.md b/skills/hyperframes/SKILL.md
index 807e6f288..8199f5dc5 100644
--- a/skills/hyperframes/SKILL.md
+++ b/skills/hyperframes/SKILL.md
@@ -148,13 +148,20 @@ Layered effects (glow behind text, shadow elements, background patterns) and z-s
### Composition Clips
-| Attribute | Required | Values |
-| ---------------------------- | -------- | -------------------------------------------- |
-| `data-composition-id` | Yes | Unique composition ID |
-| `data-start` | Yes | Start time (root composition: use `"0"`) |
-| `data-duration` | Yes | Takes precedence over GSAP timeline duration |
-| `data-width` / `data-height` | Yes | Pixel dimensions (1920x1080 or 1080x1920) |
-| `data-composition-src` | No | Path to external HTML file |
+| Attribute | Required | Values |
+| ---------------------------- | -------- | ----------------------------------------------------------------- |
+| `data-composition-id` | Yes | Unique composition ID |
+| `data-start` | Yes | Start time (root composition: use `"0"`) |
+| `data-duration` | Yes | Takes precedence over GSAP timeline duration |
+| `data-width` / `data-height` | Yes | Pixel dimensions (1920x1080 or 1080x1920) |
+| `data-composition-src` | No | Path to external HTML file |
+| `data-variable-values` | No | JSON object of per-instance variable overrides on a sub-comp host |
+
+On the root `` element:
+
+| Attribute | Required | Values |
+| ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
+| `data-composition-variables` | No | JSON array of declared variables (id/type/label/default) — drives Studio editing UI and provides defaults for `getVariables()` |
## Composition Structure
@@ -184,6 +191,75 @@ Sub-composition structure:
Load in root: ``
+## Variables (Parametrized Compositions)
+
+Render the same composition with different content — title, theme color, prices, captions — without editing the source HTML.
+
+**Three-step pattern:**
+
+1. **Declare** variables on the composition's `` root with `data-composition-variables`. Each entry needs `id`, `type` (one of `string`, `number`, `color`, `boolean`, `enum`), `label`, and `default`. Enum entries also need `options: [{value, label}, ...]`.
+2. **Read** the resolved values inside the composition's script with `window.__hyperframes.getVariables()`. Returns the merged result of declared defaults + per-instance overrides + CLI overrides.
+3. **Override** at render time with `npx hyperframes render --variables '{...}'` (top-level) or with `data-variable-values='{...}'` on the host element (per-instance for sub-comps).
+
+```html
+
+
+
+
+
+
+
+
+
+```
+
+```bash
+# Dev preview uses declared defaults
+npx hyperframes preview
+
+# Render with overrides
+npx hyperframes render --variables '{"title":"Q4 Report","theme":"dark"}' --output q4.mp4
+
+# Or from a JSON file
+npx hyperframes render --variables-file ./vars.json
+```
+
+**Sub-composition per-instance values:** the same `getVariables()` works inside sub-comps loaded via `data-composition-src`. Each host element passes its own values:
+
+```html
+
+
+```
+
+The runtime layers each host's `data-variable-values` over the sub-comp's declared defaults on a per-instance basis, so the same source can be embedded multiple times with different content.
+
+**Rules of thumb:**
+
+- Always provide a sensible `default` for every declared variable. Dev preview uses defaults — without them, the composition won't render correctly until `--variables` is provided.
+- Read variables once at the top of the script (`const { title } = ...`), not inside frame loops or event handlers — `getVariables()` allocates a fresh object per call.
+- Use `--strict-variables` in CI to fail fast on undeclared keys or type mismatches.
+- Variable types are validated at render time. `string`, `number`, `boolean`, and `color` (hex string) check `typeof`; `enum` checks the value is in the declared `options`.
+
## Video and Audio
Video must be `muted playsinline`. Audio is always a separate `