Variables & templating
The {{$VAR}} token model and JSON variants manifest in HyperFrames — define variables, set defaults, and render a personalized video per row of data.
A HyperFrames composition is a template the moment it contains a {{$NAME}} token. The renderer substitutes those tokens before the page boots, so the same composition produces one video, a thousand videos, or a video per row of a CSV without any change to the markup.
What you'll learn
- The
{{$VAR}}token model and where substitution happens - How to declare defaults so a composition stays previewable
- The CLI surface:
--varsfor one render,--variantsfor a manifest - How to escape literal
{{if you really need it
The token model
Anywhere in the HTML — attribute values, text content, inline <style>, inline <script> — write {{$VAR}}. The renderer does a single literal substitution pass on the raw document text before handing it to Chrome. There is no JavaScript evaluation, no expression language, no conditional logic. It is text in, text out.
<div style="background: {{$ACCENT}}">
Welcome, {{$NAME}}.
</div>Variable names match [A-Z][A-Z0-9_]* by convention. The substitution is case-sensitive.
Defaults keep the composition previewable
Declare defaults in a <script type="application/hyperframes-vars"> block at the top of the document. The renderer reads it, removes it, then substitutes.
<script type="application/hyperframes-vars">
{
"NAME": { "default": "Ada Lovelace" },
"ACCENT": { "default": "#f97316" }
}
</script>When you open the file directly in a browser or the playground, defaults fill the tokens so you see real content instead of {{$NAME}} on screen. When the CLI runs, supplied values override defaults; any unsupplied variable falls back to its default. Missing a variable with no default is a render error.
Live substitution
Move a knob, watch the substitution happen. This is the exact same pipeline the renderer runs, just wired to a UI instead of a CLI flag.
Source and result
<!doctype html>
<html>
<script type="application/hyperframes-vars">
{
"NAME": { "default": "Ada" },
"ACCENT": { "default": "#f97316" }
}
</script>
<body style="margin:0;background:#0a0a0a;color:#fafafa;font-family:ui-sans-serif,system-ui;">
<div data-width="1920" data-height="1080" data-duration="3"
style="width:100%;aspect-ratio:16/9;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:16px;">
<div style="font-size:64px;font-weight:800;">Hello, {{$NAME}}.</div>
<div style="height:6px;width:200px;background:{{$ACCENT}};border-radius:3px;"></div>
</div>
</body>
</html>Driving it from the CLI
For a one-off render, pass --vars:
hyperframes render greeting.html \
--out ada.mp4 \
--vars NAME="Ada" ACCENT="#f97316"For many renders, hand the CLI a variants manifest — either JSON or CSV. Each entry becomes one output file.
[
{ "_out": "ada.mp4", "NAME": "Ada", "ACCENT": "#f97316" },
{ "_out": "linus.mp4", "NAME": "Linus", "ACCENT": "#22d3ee" },
{ "_out": "grace.mp4", "NAME": "Grace", "ACCENT": "#a78bfa" }
]hyperframes render greeting.html --variants people.jsonThe _out field controls the output filename. Any other field whose name matches a declared variable is substituted. Unknown fields are ignored. Variants render in parallel up to the worker pool size.
Variables are part of the determinism contract
The composition hash that the manifest records includes the resolved variable values. Two renders with the same composition and the same variables produce the same bytes. Two renders with the same composition and different variables produce different bytes, by design — and the manifest tells you exactly which variables changed.