WebMotion is a browser-native alternative to Remotion. No headless Chrome, no FFmpeg, no render farm: compose in the DOM or on a canvas, encode with WebCodecs, and download an MP4, all client-side.
It rests on one rule: everything visible is a pure function of the current frame. Seeking to frame N always produces the same image, whether it's the first frame rendered or the ten-thousandth, in preview or in export. That is what makes rendering seekable, cacheable, and frame-accurate without a server.
npm install @superhq/webmotionImport the elements entry once and the scene is just markup:
<script type="module">
import "@superhq/webmotion/elements";
</script>
<w-composition width="1280" height="720" fps="30" duration="150" autoplay>
<w-defs>
<w-animation name="fade-up">
<w-animate property="opacity" from="0" to="1" start="0" end="18" easing="easeOutCubic"></w-animate>
<w-animate property="y" from="40" to="0" start="0" end="18" easing="easeOutCubic"></w-animate>
</w-animation>
</w-defs>
<w-rect x="0" y="0" width="1280" height="720" fill="#0d101b"></w-rect>
<w-sequence from="12">
<w-text motion="fade-up" x="0" y="250" width="1280" align="center"
font="700 96px system-ui" color="#f5f6f8">Author in HTML.</w-text>
</w-sequence>
</w-composition>A <w-sequence from duration> shifts the frame origin for its subtree; a <w-animate> is one tween of one property over a frame window, pure function of the local frame, so the live preview and the exported video are pixel-for-pixel the same scene. Tweens can be written inline as children for one-offs, or defined once in <w-defs> and applied by name with motion="...", class-like. The full rules (scoping, ordering, staggering through sequences) are in the motion spec. Repetition is declarative too: <w-for> stamps templated children from <w-data> JSON with {path + arithmetic} placeholders (template spec); sound is <w-audio> on the same timeline (audio spec).
The element drives itself:
const comp = document.querySelector("w-composition");
comp.play();
comp.seek(42); // deterministic: always the exact same image
const blob = await comp.export(); // MP4, encoded in the browserThe programmatic API underneath is a small component contract: renderFrame receives a frame index and draws.
import { Composition, Runtime, Layer, Sequence, CanvasRenderer, interpolate, Easing } from "@superhq/webmotion";
const composition = new Composition({ width: 1280, height: 720, fps: 30, durationInFrames: 180 });
class Title {
mount() {}
renderFrame({ ctx, frame, width, height }) {
ctx.globalAlpha = interpolate(frame, [0, 20], [0, 1], { easing: Easing.easeOutCubic, extrapolateRight: "clamp" });
ctx.fillStyle = "#fff";
ctx.font = "600 84px system-ui";
ctx.textAlign = "center";
ctx.fillText("WebMotion", width / 2, height / 2);
}
destroy() {}
}
const runtime = new Runtime({
composition,
renderer: new CanvasRenderer(1280, 720, { canvas }),
layers: [new Layer({ component: new Title(), sequence: new Sequence({ from: 20 }) })],
});
await runtime.renderFrame(30); // draws exactly frame 30, every timePrefer real DOM over canvas drawing? The @superhq/webmotion/html-in-canvas backend renders live HTML and rasterizes it per frame. It is named after, and tracks, the WICG html-in-canvas proposal: today a foreignObject rasterizer stands in as the polyfill, and the native APIs take over when browsers ship them. See the architecture notes for that and everything else.
Live: superhq-ai.github.io/webmotion
Or run locally:
git clone https://github.com/superhq-ai/webmotion && cd webmotion
npm install
npm run demoTwo launch-grade films, each authored entirely as markup with the source shown alongside: a keynote-style launch film (staged beats, letter-tracked reveal, generated imagery) and a stats film with deterministic count-up counters built on a custom component. Scrub, play, and export real MP4s in-browser. Needs a Chromium-based browser (WebCodecs H.264 and OffscreenCanvas).
skills/webmotion/ is an installable agent skill that teaches AI coding agents to author WebMotion scenes: the element reference, motion rules, styling guidance, export wiring, and launch-film recipes with pacing craft.
Install with the skills CLI (works with Claude Code, Cursor, Copilot, and 15+ other agents):
npx skills add superhq-ai/webmotionOr copy the folder directly for Claude Code:
npx -y degit superhq-ai/webmotion/skills/webmotion ~/.claude/skills/webmotionThen ask for a video ("make me a 10 second launch film for X") and the agent knows the format.
npm test # unit tests (Node, no browser needed)
npm run typecheck
npm run build # tsc -> dist/MIT. The HTML rasterizer is a derivative of MIT-licensed work by repalash; see CREDITS.
