Web graphics
A web graphic is a small HTML, CSS and JavaScript page that sits on a slide as a layer: a chart that grows, a number that counts up, a list that reveals line by line. It steps through named states, and cues in your script step it while you talk.
What a web graphic is
A graphic is a folder of files: an entry page, its scripts and styles, any images or fonts it needs, and a graphic.json that names it and lists its steps. When you add it, ArcLight embeds the whole folder in your show document, like an image. Moving or deleting the folder afterward doesn't affect the show.
The page's background is transparent, so the slide shows through. ArcLight draws the page offscreen and treats it like any other layer: it appears on the canvas, in program output, in the presenter view, in recordings and in the virtual camera.
Add a graphic to a slide
Select the slide, then choose in the toolbar.
Pick the graphic's folder (the one with
index.htmlandgraphic.json) and click Open.The graphic lands centered on the slide at the size in its
graphic.json, named after itsname. Move and resize it like any layer. Its settings are in .
If the folder can't be used, ArcLight tells you why: no files, a graphic.json that isn't valid, an entry page that's missing, or a folder over 5 MB. Hidden files in the folder are skipped.
To change a graphic's files, edit the folder and add it again. An AI agent can also replace a graphic's files in place.
The bundle
Everything the page loads must be inside the folder. Link files with relative paths, such as app.js or fonts/Inter.woff2. Libraries have to be copied in too: pages can't load anything from the internet.
graphic.json
Every field is optional, but a graphic without steps can't be stepped from the script.
| Field | What it does |
|---|---|
name | The layer's name when you add it. Script cues can refer to the graphic by this name. |
entry | The page to open. Defaults to index.html. |
size | [width, height] in canvas points: the layer's size when you add it. Without it, the layer takes half the canvas in each direction. |
steps | The graphic's states, in order, such as ["2023", "2024", "2025"]. |
params | Default values handed to the page, such as {"currency": "USD"}. You can override them per layer in the inspector. |
{
"name": "Revenue chart",
"entry": "index.html",
"size": [1080, 600],
"steps": ["2023", "2024", "2025"],
"params": { "currency": "USD" }
}The page is laid out at the layer's size. Resize the layer and the page's viewport resizes with it, so design with relative units or read the window size.
The window.arclight runtime
ArcLight adds a small object, window.arclight, to every graphic before its own scripts run.
| API | What it does |
|---|---|
arclight.params | The params from graphic.json, with the layer's own parameters on top. |
arclight.steps | The step names, in order. |
arclight.step, arclight.stepIndex | The current step and its position in steps. |
arclight.onStep((name, index, {animate}) => …) | Called with the step to show. It's also called once when the page loads, with animate false. Draw that state, and animate into it only when animate is true. |
arclight.onEnter(fn), arclight.onExit(fn) | Called when the graphic's page starts showing and after it stops being shown. |
arclight.time() | Seconds on ArcLight's clock for this page. |
arclight.ready() | Optional. Tells ArcLight the first frame is ready to capture. |
arclight.mode | "program" while it runs on a slide, "render" when ArcLight draws a single still of it. |
Steps are absolute. onStep tells you which state to show, not "go forward one". While you rehearse you can jump back or skip ahead, so render the state from the step you're given and never assume what came before it.
Animate by time, never by counting frames
ArcLight runs each graphic on its own clock. performance.now(), Date.now() and requestAnimationFrame all follow that clock, and CSS animations, CSS transitions and Web Animations are paused and moved to the right moment on every frame. That's what keeps a graphic smooth in a page no one is looking at, and makes every frame repeatable: the same step at the same moment always looks the same.
So:
- Use CSS animations and transitions, or the Web Animations API (
element.animate()). - In a
requestAnimationFrameloop, compute from the timestamp it passes you, or fromarclight.time(). - Don't add a fixed amount per frame, and don't animate with
setInterval. Frames can arrive at a different rate than you expect.
What a graphic can't do
- No network. Pages load only files from their own bundle. Links and redirects elsewhere are refused.
- No fetching.
fetchandXMLHttpRequestare blocked, even for files in the bundle. Put data inparamsor in a script file. - Nothing is kept. Cookies and storage don't persist between runs.
- No autoplaying media. Audio and video elements in the page won't start on their own. Put video on the slide as a video layer instead.
Errors, including exceptions thrown inside your onStep handler, are caught and listed under Page errors in the inspector.
A complete example
This graphic shows a label, counts a number up, then reveals a note: three steps. Make a folder with these two files and add it with .
graphic.json
{
"name": "Big Number",
"size": [800, 360],
"steps": ["intro", "value", "note"],
"params": {
"label": "Monthly listeners",
"value": 48000,
"note": "Up 12% on last year"
}
}index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
html, body { margin: 0; height: 100%; background: transparent; }
body { display: grid; place-content: center; text-align: center;
font-family: -apple-system, sans-serif; color: white; }
#label { font-size: 34px; opacity: 0.8; }
#number { font-size: 150px; font-weight: 700; line-height: 1; opacity: 0; }
#note { font-size: 30px; color: #edb341; opacity: 0; }
</style>
</head>
<body>
<div id="label"></div>
<div id="number">0</div>
<div id="note"></div>
<script>
const p = arclight.params;
const label = document.getElementById("label");
const number = document.getElementById("number");
const note = document.getElementById("note");
label.textContent = p.label;
note.textContent = p.note;
// Counting runs on ArcLight's clock, so every frame is exact.
let countStart = null;
function drawCount() {
if (countStart === null) return;
const t = Math.min((arclight.time() - countStart) / 1.2, 1);
const eased = 1 - Math.pow(1 - t, 3);
number.textContent = Math.round(p.value * eased).toLocaleString();
if (t < 1) requestAnimationFrame(drawCount);
}
function fade(el, visible, animate) {
el.getAnimations().forEach(a => a.cancel());
el.style.opacity = visible ? 1 : 0;
if (animate) {
el.animate([{ opacity: visible ? 0 : 1, transform: "translateY(12px)" },
{ opacity: visible ? 1 : 0, transform: "none" }],
{ duration: 500, easing: "ease-out" });
}
}
// Steps are absolute: draw the state for this step, whatever came before.
arclight.onStep((name, index, { animate }) => {
const showsNumber = index >= 1;
fade(number, showsNumber, animate && name === "value");
fade(note, index >= 2, animate && name === "note");
if (showsNumber && animate && name === "value") {
countStart = arclight.time();
requestAnimationFrame(drawCount);
} else {
countStart = null;
number.textContent = showsNumber ? p.value.toLocaleString() : "0";
}
});
arclight.ready();
</script>
</body>
</html>To show a different number on another slide, select the layer and change value under Parameters in the inspector. The file stays the same.
Step it from the script
In the script, a trigger cue in double braces steps a graphic when the prompter reaches it. Triggers never change slides.
| Write | What happens |
|---|---|
{{Big Number: value}} | The graphic named Big Number goes to its value step. |
{{value}} | The graphic that has a step called value goes to it. If the slide has only one graphic, that one. |
{{next}} | Runs the live slide's next build, as if you'd pressed the next key. It doesn't step a graphic, and does nothing unless the show is playing. |
Names and steps ignore case and extra spaces. In the script editor a trigger becomes a purple chip, such as ◆ Big Number: value or ◆ Next build.
Which slide a trigger belongs to
ArcLight looks for the graphic on the slide named by the last [[Slide]] cue before the trigger. So write the slide cue first, then the triggers for that slide's graphics:
[[Audience]] Here's where we started this year. {{value}} Forty-eight thousand people a month. {{note}} And that's up twelve percent.- A trigger that names a graphic that isn't on that slide, or a bare step that matches no graphic there, stays in the script as plain text, so you can see it didn't match. A misspelled step is not caught this way:
{{Revenue: 2026}}is kept, and when it fires the graphic is asked for a step it doesn't have. Check step names against the graphic's steps (the Graphic inspector lists them). - If the prompter reaches a trigger while a different slide is up, the trigger does nothing, rather than stepping the wrong graphic.
- A bare
{{step}}written before any slide cue steps whichever graphic has that step on the slide that's up. - When there are several graphics on a slide, use the
{{Layer: step}}form. The layer name is the one in the inspector's Layers list, which starts as thenameingraphic.json.
Rehearse steps in the inspector
Select the graphic and open :
- Steps lists every step. Click one to show it now, animated, just as a script cue would.
- Starts at picks the step shown when the slide first appears, before any trigger. The default is the first step.
- Parameters holds this layer's values as JSON, on top of the defaults from
graphic.json. Click Apply. - Page errors appears when the page has thrown errors, with the most recent ones.
While the show isn't playing, triggers in the script step the graphic on the selected slide, so you can read through the prompter and watch each step land. When you press Play, every graphic goes back to its starting step.
One page per graphic. Layers that show the same graphic with the same parameters share one running page, so a graphic keeps its current step across Magic Move and on duplicated slides. To carry a graphic onto the next slide, duplicate the slide or copy the layer rather than adding the folder again: a graphic added twice runs as two separate pages.
Let an agent write it
AI agents connected over MCP can write a graphic's files, add it to a slide, render each step to check it, and put the trigger cues into the script. The producer skill ships a kit of ready-made data graphics. See Set up AI agents.