recital Github

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.

Read the docs
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/. Add narration lines and run npx recital narrate example for the voiced, captioned video. Writing narration
Or let an agent write it
npx recital skill adds a skill that plans the demo, writes the scenario, checks the frames and writes the lines.