Getting started
Install the packages, compile a scene, resolve a frame, and render it with Remotion.
Install
The @motionactor/* line is published to GitHub Packages, not public npm. Point the
scope at the registry in your project's .npmrc:
@motionactor:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}GitHub Packages requires authentication for reads as well as writes, so NODE_AUTH_TOKEN
must hold a token with the read:packages scope. In CI, secrets.GITHUB_TOKEN is enough.
Keep the token in the environment
.npmrc interpolates ${NODE_AUTH_TOKEN} at install time. Commit the file with the
variable reference, never with a token in it.
pnpm add @motionactor/core @motionactor/dom@motionactor/core is the runtime — actors, signals, scene compilation, frame
resolution. @motionactor/dom is the DOM/React backend and the Remotion seam. See
Packages for the rest of the line.
Your first scene
Declare an actor
An actor owns state and the commands that move it. LayoutActor is the base for anything
that occupies space — it already declares layout, transform, appearance,
flexItem and gridItem.
import { LayoutActor, signal, type Duration } from "@motionactor/core";
export class Card extends LayoutActor {
progress = signal(0);
*reveal({ duration }: { duration: Duration }) {
yield* this.progress.tween(1, duration);
}
}reveal is a generator. yield* on a tween is what consumes authored time — the method
reads like an instruction and compiles into a timeline record.
Compile a scene
createScene runs the generator once and records everything it authored. It does not
render, and it does not play.
import { createScene } from "@motionactor/core";
export const FPS = 30;
export const compiled = createScene(
function* (ctx) {
const card = ctx.spawn(Card);
yield* card.reveal({ duration: 1 });
yield* ctx.wait(1);
return { card };
},
{ fps: FPS },
);Returning { card } makes it a scene export — typed access to that actor from every
resolved frame, with no lookup by string and no casts.
Resolve a frame
The scene above is the code on this page, compiled and running. Drag the scrubber: every
position calls compiled.at(frame) fresh, so scrubbing backwards is the same operation as
playing forwards.
compiled.fps; // 30
compiled.totalDuration; // 60 frames — 1s reveal + 1s hold
compiled.at(0).scene.card.progress; // 0
compiled.at(15).scene.card.progress; // 0.5
compiled.at(30).scene.card.progress; // 1Each call is independent. Asking for frame 30 does not require having asked for frame 29, and asking twice returns the same answer.
Render it
defineSceneComposition wraps a compiled scene as a Remotion composition: it supplies the
render runtime, binds the Remotion host, and derives duration from the scene through
calculateMetadata.
import { AbsoluteFill, Composition, registerRoot } from "remotion";
import { defineSceneComposition } from "@motionactor/dom";
import { compiled, FPS } from "./scene";
export const CardComposition = defineSceneComposition({
id: "Card",
width: 1280,
height: 720,
fps: FPS,
buildScene: () => compiled,
render: ({ compiled, frame }) => {
const { card } = compiled.at(frame).scene;
return (
<AbsoluteFill className="items-center justify-center bg-slate-950">
<div
className="rounded-xl bg-indigo-600 p-8 text-6xl text-white"
style={{ opacity: card.progress, transform: `scale(${0.9 + card.progress * 0.1})` }}
>
MotionActor
</div>
</AbsoluteFill>
);
},
});
export function RemotionRoot() {
return <Composition {...CardComposition.remotion} />;
}
registerRoot(RemotionRoot);The view is a thin reader. It never decides when anything happens — it draws the state the runtime resolved for this frame.
What to read next
How it works
Why compile and resolve are separate stages, and what each one may do.
Choosing a primitive
signal vs reactiveSignal vs derived vs channel, with the rule for each.
Complete Remotion example
The full typechecked example: slots, children, channels, scene exports.
Creating actors
Naming, state layout, render paths, and the rules that keep actors reusable.
Introduction
A generator-driven actor runtime for deterministic, frame-based video. Author motion once as code; resolve any frame as a pure function.
How it works
Compile records authored intent once. Resolution reconstructs actor state for any frame from that record. Nothing else may influence a frame.