Most animation tooling assumes a human at the wheel: a timeline, a scrubber, easing curves you nudge by eye. I wanted the opposite — a way to say what an animation should do and have something check that the pixels actually did it. That's Choreo.
The idea in one screen
You write a scene:
scene "signup-success" size 800x450 duration 2600ms background #f1f5f9
entities
card = rect 320x220 fill #ffffff radius 12
ring = circle 30 stroke #16a34a stroke-width 4
tick = path "M8 16 L14 22 L24 10" size 32x32 stroke #16a34a stroke-width 4
label = text "Account created" size 18 weight 600 color #0f172a
layout
center card
inside ring card top 36
center tick in ring
below label ring gap 18
motion
seq
fadeIn card 500ms easeOut
par
pop ring 400ms
drawOn tick 450ms easeOut
slideIn label from bottom 10 400ms easeOut
checks
during 0..500 : fadesIn(card)
during 500..900 : grows(ring) and fadesIn(ring)
at 1800 : below(label, ring)
always : inside(ring, card) and inside(label, card)
at end : settled(all)
It compiles to a self-contained SVG + CSS animation. Then the checks block becomes a spec the verifier runs against the rendered result:
npx choreoc examples/signup-success.choreo -o out --verify
# → out/signup-success.html the animation
# → out/signup-success.spec.mjs the checks, as a verifier spec
# → 19/19 checks passed
Why not just screenshot-diff it?
Visual-regression tools (Chromatic, Percy, Applitools) compare frames. They're great at "this looks different from last time" and useless at "the toast slid in before it faded and had settled by 1.2s." Choreo's verifier samples the real trace — element positions, opacity, size over time, driven by Playwright — and asserts first-order predicates over it: movesLeft, fadesIn, inside(a, b), settled(all), with time windows (during, at, always, at end).
The verifier is a separate package and works on any web animation, not just Choreo output — CSS keyframes, GSAP, Motion, a Remotion export. If you have an animation and a claim about how it should move, you can check the claim:
export default {
name: 'signup-flow',
target: 'file:///…/signup-flow.html',
duration: 2600,
entities: { toast: '#toast', msg: '#msg' },
checks: [
{ during: [0, 400], assert: ['movesLeft(toast)', 'fadesIn(toast)'] },
{ at: 1200, assert: ['visible(toast)', 'inside(msg, toast)'] },
{ atEnd: true, assert: ['fadedOut(toast)'] },
],
};
Where this came from, and what it isn't
It began as an internal probe: can an LLM author animation reliably? On its own, not really — roughly half right. The research I leaned on (MoVer, SIGGRAPH 2025) shows that wrapping generation in a formal verify-and-correct loop closes most of that gap. Choreo is that shape made concrete: a grammar small enough for a model to hit from a spec, and a checker that catches it when it's wrong.
It is a research artifact, released as-is under MIT. No roadmap promise, no support SLA. There's a cross-framework benchmark in the repo if you want to poke at whether the DSL actually beats raw CSS (short version: the durable win is authoring cost, not correctness). Issues and PRs welcome; treat it as a foundation, not a maintained service.
- Repo: https://github.com/JieGouAI/choreo
npm i @choreo-oss/lang @choreo-oss/verify
Disclosure: this came out of JieGou, an ops-automation company I'm building; Choreo is an open side-output, not a product we sell.
Top comments (0)