Script your demos
Write what to click and what to say. recital drives the browser with a calm cursor, records at 60 fps and paces each step to the voice. When the product changes, record it again.
export default { url: "https://www.rijksmuseum.nl/en/collection", speed: 1.25, cursors: "macos", camera: [ { at: "search", center: [600, 720], zoom: 1.5 }, { at: "results", zoom: 1, ms: 1000 }, ], steps: [ { type: "Night Watch", label: "search", into: { ui: "#search-input" } }, { press: "Enter", label: "results", waitFor: { ui: ".art-grid-item-link" } }, // A slow page: keep 0.6 s of the wait { click: { ui: ".art-grid-item-link >> nth=0" }, label: "open", waitFor: { ui: "h1.object-title" }, cut: 600 }, // The painting’s own zoom controls { click: { ui: "[aria-label='Zoom in']" }, label: "zoom" }, { click: { ui: "[aria-label='Zoom in']" } }, { click: { ui: "[aria-label='Zoom out']" }, label: "back" }, { scroll: { ui: "h3:has-text('Stories')" }, label: "stories", flicks: 2 }, ],};
The scenario, shortened, and the take it recorded. Click a step to jump to it.
What a take gets
Restrained defaults, so the product is what people watch.
- Paced to the voice
- Each narrated step waits for its line, so a new take always fits.
- A calm cursor
- Eased, slightly curved moves and a soft click. macOS, minimal or Figma cursors.
- Captions
- The narration, burned into the video, with .vtt and .srt beside it.
- Camera
- Zoom in on a step and ease back out, from two lines in the scenario.
- A free local voice
- Kokoro runs offline with no key. ElevenLabs when you want a studio voice.
- Recorded again in CI
- After each deploy, headless. A step that stops working fails the job. CI guide
Adapters, for apps like Figma
Some apps need more than selectors: a canvas, a plugin’s own window. An adapter teaches recital the app. Figma’s is built in: open a plugin in a real file, find layers by name, and record it at work, here narrated by an ElevenLabs voice. Figma guide, write an adapter
const plugin = (selector) => ({ plugin: selector }); export default { file: "https://www.figma.com/design/EuLO3NTXzr…", plugin: { name: "Data Mapper" }, narrator: { provider: "elevenlabs", voice: "Lily" }, narration: [ { at: "add · open the form", text: "Start by adding…" }, ], steps: [ { click: plugin("role=button[name='Add data source']"), label: "add · open the form" }, { type: SOURCE, into: plugin("#data-source-url") }, // A layer on the canvas, by its path { click: { canvas: "collection/art card" } }, { click: plugin("role=button[name='Create mapping']") }, { click: plugin("role=button[name='Link component']") }, { click: plugin("text=/^title\\b/") }, { click: plugin("role=button[name='Save changes']") }, { click: plugin("role=button[name='Create copies']"), waitFor: { ui: "text=/^Created/" } }, ],};
Shortened from 31 steps. Turn the sound on to hear the voice.
Start
Demos live in a folder of their own, beside your app.
$ npm i -D recitaljs
$ npx playwright install chromium
$ npx recital init # an example scenario
$ npx recital doctor # checks ffmpeg, Chromium, the voice
$ npx recital demo example
- Needs
- Node 20 or later and
ffmpeg, on macOS or Linux. - Then give it a voice
- The take lands in
output/example/. Addnarrationlines and runnpx recital narrate examplefor the voiced, captioned video. Writing narration - Or let an agent write it
npx recital skilladds a skill that plans the demo, writes the scenario, checks the frames and writes the lines.