Skip to content
MotionActor
Esc
↑↓navigate↵open⌘Jpreview
On this page

Remotion card

A complete Remotion composition in two files, with a declared child, a derived value read against the viewport and typed scene exports.

A card widens and fades in over one second, then holds for one. The example depends on no actor families and no media.

card.reveal({ duration: 1 })
30f
ctx.wait(1)
30f
30 fps
0
1s · 30f
2s · 60f

60 frames. progress reaches 1 at frame 30 and holds; revealWidth follows it, scaled to three quarters of the stage.

scene.ts

import {
  class Actor<TEvents extends ActorEventMap = {}>Actor,
  class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor,
  function child<TBlueprint extends ActorClass<object>, TOwner extends object = object>(blueprint: TBlueprint, ...[overrides]: ChildOverridesArg<TBlueprint, TOwner>): ChildField<TBlueprint, TOwner>child,
  function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
,
function derived<TThis, T>(fn: (this: TThis) => T): DerivedDefinition<T, TThis>derived,
const runtime: {
    frame(): number;
    fps(): number;
    timeSeconds(): number;
    viewport(): SceneViewport;
}
runtime
,
function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
,
type type Duration = numberDuration, } from "@motionactor/core"; export class class CaptionCaption extends class Actor<TEvents extends ActorEventMap = {}>Actor { Caption.text: ActorFieldSignal<string>text = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
("MotionActor");
} export class class CardCard extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor { Card.caption: ChildField<typeof Caption, object>caption = child<typeof Caption, object>(blueprint: typeof Caption, overrides?: ChildOverrides<typeof Caption, object> | undefined): ChildField<typeof Caption, object>child(class CaptionCaption); Card.progress: ActorFieldSignal<number>progress = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
(0);
// Three quarters of the stage once revealed. Card.revealWidth: DerivedDefinition<number, unknown>revealWidth = derived<unknown, number>(fn: (this: unknown) => number): DerivedDefinition<number, unknown>derived(() => this.Card.progress: () => numberprogress() * 0.75 *
const runtime: {
    frame(): number;
    fps(): number;
    timeSeconds(): number;
    viewport(): SceneViewport;
}
runtime
.function viewport(): SceneViewportviewport().SceneViewport.width: numberwidth);
*
Card.reveal({ duration }: {
    duration: Duration;
}): Generator<number, void, unknown>
reveal
({ duration: numberduration }: { duration: numberduration: type Duration = numberDuration }) {
yield* this.Card.progress: ActorFieldSignal<number>progress.LiveSignal<number>.tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>
Tween to target over authored seconds — must yield*
tween
(1, duration: numberduration);
} } export const
const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>
scene
=
createScene<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>, options?: SceneDefinitionOptions<...> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
(function* (ctx: SceneContextctx) {
const const card: Cardcard = ctx: SceneContextctx.
CompileContext.spawn<Card>(definition: ActorClass<Card>, overrides?: {
    id?: undefined;
    cursor?: undefined;
    cursorSeconds?: undefined;
    sceneFps?: undefined;
    onInit?: undefined;
    onEnter?: undefined;
    onExit?: undefined;
    enter?: undefined;
    exit?: undefined;
    parent?: undefined;
    measuredBox?: undefined;
    caption?: {
        text?: SignalInputValue<string> | undefined;
    } | undefined;
    progress?: SignalInputValue<number> | undefined;
    revealWidth?: undefined;
    reveal?: undefined;
    layout?: {
        x?: SignalInputValue<LayoutValue> | undefined;
        y?: SignalInputValue<...> | undefined;
        width?: Readonly<...> | ... 1 more ... | undefined;
        height?: Readonly<...> | ... 1 more ... | undefined;
    } | undefined;
    ... 10 more ...;
    bounds?: undefined;
} | undefined, options?: {
    ...;
} | undefined): Card (+1 overload)
spawn
(class CardCard);
yield* const card: Cardcard.
Card.reveal({ duration }: {
    duration: Duration;
}): Generator<number, void, unknown>
reveal
({ duration: numberduration: 1 });
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1); return { card: Cardcard, caption: ChildField<typeof Caption, object>caption: const card: Cardcard.Card.caption: ChildField<typeof Caption, object>caption }; });
  • progress is the authored state, and reveal({ duration }) is the command that moves it. One second is 30 frames because the composition builds the scene at 30 fps.
  • caption = child(Caption) declares the card’s caption and creates it with the card. See Children.
  • revealWidth is a derived of progress and the stage width, which it reads with runtime.viewport(). The composition builds the scene at its own size, so a build at another size gives another width over the same timeline.
  • The scene returns the card and its caption, so the view reads them as typed scene.card and scene.caption.

composition.tsx

import { 
const AbsoluteFill: React.ComponentType<Omit<AbsoluteFillElementProps, "durationInFrames" | "freeze" | "from" | "hidden" | "name" | "showInTimeline" | "trimBefore"> & InteractiveBaseProps & {
    readonly stack?: string | undefined;
}>
AbsoluteFill
, const Composition: <Schema extends AnyZodObject, Props extends Record<string, unknown>>(props: CompositionProps<Schema, Props>) => import("react/jsx-runtime").JSX.Element | nullComposition, const registerRoot: (comp: React.FC<{}>) => voidregisterRoot } from "remotion";
import { const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>
The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`, `createRenderRuntime({ backends: [dom] })`.
dom
} from "@motionactor/dom/react";
import { const createRenderRuntime: (input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>) => ReactRenderRuntime
The floor's `createRenderRuntime` for React: every backend's host is a React component and its stages wrap React nodes, so mixed backends (each with its own host props) compose without naming a type.
createRenderRuntime
, const defineComposition: DefineComposition<ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>
A composition of a `createScene` definition (`scene`), painted by `render`, or by default by `SceneRenderer` over `background`.
defineComposition
} from "@motionactor/renderers/react";
import {
function toRemotionComposition<P extends Record<string, unknown>, T>(input: CompositionDefinition<P, T>): {
    id: string;
    width: number;
    height: number;
    fps: number;
    defaultProps: P;
    durationInFrames: number;
    component: React.FC<P>;
    calculateMetadata: CalculateMetadataFunction<P>;
}
A Remotion composition for `input`: like every host, it mounts the runtime media family after the runtime's own renderers. The mounted instance builds at Remotion's resolved viewport, which `calculateMetadata` or a Player may set apart from the definition's.
toRemotionComposition
} from "@motionactor/remotion";
import {
const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>
scene
} from "./scene";
export const
const card: CompositionDefinition<HostInputArgs<Readonly<Record<never, SceneInput>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>
card
=
defineComposition<SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>>(definition: SceneCompositionInput<SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>, ReactBackendHost, React.ReactNode, React.ReactElement<...>>): CompositionDefinition<...> (+1 overload)
`scene`'s inputs are the props.
defineComposition
({
id: stringid: "ActorCard", width: numberwidth: 1280, height: numberheight: 720, fps: numberfps: 30, runtime: RenderRuntime<ReactBackendHost, React.ReactNode>runtime: function createRenderRuntime(input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>): ReactRenderRuntime
The floor's `createRenderRuntime` for React: every backend's host is a React component and its stages wrap React nodes, so mixed backends (each with its own host props) compose without naming a type.
createRenderRuntime
({ backends: readonly AnyBackend<ReactBackendHost, React.ReactNode>[]backends: [const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>
The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`, `createRenderRuntime({ backends: [dom] })`.
dom
] }),
scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>
scene
,
render?(args: CompositionRenderArgs<HostInputArgs<Readonly<Record<never, SceneInput>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>): React.ReactElement<unknown, string | React.JSXElementConstructor<any>>
render
: ({
compiled: CompiledScene<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>
compiled
, frame: numberframe }) => {
const { const card: ResolvedActorOfInstance<Card> | undefinedcard, const caption: ResolvedActorOfInstance<Caption> | undefinedcaption } =
compiled: CompiledScene<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>
compiled
.
CompiledScene<{ card: Card; caption: ChildField<typeof Caption, object>; }, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>
at
(frame: numberframe).
FrameState<{ card: Card; caption: ChildField<typeof Caption, object>; }>.scene: {
    card: ResolvedActorOfInstance<Card> | undefined;
    caption: ResolvedActorOfInstance<Caption> | undefined;
}
scene
;
if (!const card: ResolvedActorOfInstance<Card> | undefinedcard || !const caption: ResolvedActorOfInstance<Caption> | undefinedcaption) return <
const AbsoluteFill: React.ComponentType<Omit<AbsoluteFillElementProps, "durationInFrames" | "freeze" | "from" | "hidden" | "name" | "showInTimeline" | "trimBefore"> & InteractiveBaseProps & {
    readonly stack?: string | undefined;
}>
AbsoluteFill
/>;
// This small composition renders its typed scene exports directly. // Reusable actor renderers can instead be dispatched by SceneRenderer. return ( <
const AbsoluteFill: React.ComponentType<Omit<AbsoluteFillElementProps, "durationInFrames" | "freeze" | "from" | "hidden" | "name" | "showInTimeline" | "trimBefore"> & InteractiveBaseProps & {
    readonly stack?: string | undefined;
}>
AbsoluteFill
className?: string | undefinedclassName="items-center justify-center bg-slate-950 text-white">
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<T>.className?: string | undefinedclassName="overflow-hidden rounded-xl bg-indigo-600 p-8 text-6xl whitespace-nowrap" React.HTMLAttributes<HTMLDivElement>.style?: React.CSSProperties | undefinedstyle={{ StandardLonghandProperties<string | number, string & {}>.width?: Property.Width<string | number> | undefined
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015. **Syntax**: `auto | <length-percentage [0,∞]> | min-content | max-content | fit-content | fit-content(<length-percentage [0,∞]>) | <calc-size()> | <anchor-size()>` **Initial value**: `auto` | Chrome | Firefox | Safari | Edge | IE | | :----: | :-----: | :----: | :----: | :---: | | **1** | **1** | **1** | **12** | **4** |
@seehttps://developer.mozilla.org/docs/Web/CSS/Reference/Properties/width
width
: const card: ResolvedActorOfInstance<Card>card.revealWidth: numberrevealWidth, StandardLonghandProperties<string | number, string & {}>.opacity?: Property.Opacity | undefined
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015. **Syntax**: `<opacity-value>` **Initial value**: `1` | Chrome | Firefox | Safari | Edge | IE | | :----: | :-----: | :----: | :----: | :---: | | **1** | **1** | **2** | **12** | **9** |
@seehttps://developer.mozilla.org/docs/Web/CSS/Reference/Properties/opacity
opacity
: const card: ResolvedActorOfInstance<Card>card.progress: numberprogress }}
> {const caption: ResolvedActorOfInstance<Caption>caption.text: stringtext} </React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> </
const AbsoluteFill: React.ComponentType<Omit<AbsoluteFillElementProps, "durationInFrames" | "freeze" | "from" | "hidden" | "name" | "showInTimeline" | "trimBefore"> & InteractiveBaseProps & {
    readonly stack?: string | undefined;
}>
AbsoluteFill
>
); }, }); export function function RemotionRoot(): JSX.ElementRemotionRoot() { return <const Composition: <Schema extends AnyZodObject, Props extends Record<string, unknown>>(props: CompositionProps<Schema, Props>) => import("react/jsx-runtime").JSX.Element | nullComposition {...
toRemotionComposition<HostInputArgs<Readonly<Record<never, SceneInput>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>(input: CompositionDefinition<HostInputArgs<Readonly<Record<never, SceneInput>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>): {
    id: string;
    width: number;
    height: number;
    fps: number;
    defaultProps: HostInputArgs<Readonly<Record<never, SceneInput>>>;
    durationInFrames: number;
    component: React.FC<...>;
    calculateMetadata: CalculateMetadataFunction<...>;
}
A Remotion composition for `input`: like every host, it mounts the runtime media family after the runtime's own renderers. The mounted instance builds at Remotion's resolved viewport, which `calculateMetadata` or a Player may set apart from the definition's.
toRemotionComposition
(
const card: CompositionDefinition<HostInputArgs<Readonly<Record<never, SceneInput>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, ReactBackendHost, React.ReactNode, React.ReactElement<unknown, string | React.JSXElementConstructor<any>>>
card
)} />;
} function registerRoot(comp: React.FC<{}>): voidregisterRoot(function RemotionRoot(): JSX.ElementRemotionRoot);

defineComposition declares the composition with a render runtime that has the DOM backend. toRemotionComposition turns it into the props of a Remotion <Composition>, with the duration measured by building the scene. Compositions covers both.

The view reads the two exports and draws them itself. A composition with reusable actors draws them with SceneRenderer and registered renderers instead.

What the check asserts

import { 
const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>
scene
} from "./scene";
using
using wide: CompiledScene<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
wide
=
const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>
scene
.
SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build
({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 800, SceneViewport.height: numberheight: 450 }, fps: numberfps: 30 });
using wide: CompiledScene<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
wide
.
CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>
at
(15).
FrameState<{ ...; }>.scene: {
    card: ResolvedActorOfInstance<Card> | undefined;
    caption: ResolvedActorOfInstance<Caption> | undefined;
}
scene
.card: ResolvedActorOfInstance<Card> | undefinedcard?.progress: number | undefinedprogress; // 0.5
using wide: CompiledScene<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
wide
.
CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>
at
(15).
FrameState<{ ...; }>.scene: {
    card: ResolvedActorOfInstance<Card> | undefined;
    caption: ResolvedActorOfInstance<Caption> | undefined;
}
scene
.card: ResolvedActorOfInstance<Card> | undefinedcard?.revealWidth: number | undefinedrevealWidth; // 300
using
using narrow: CompiledScene<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
narrow
=
const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>
scene
.
SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build
({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 400, SceneViewport.height: numberheight: 225 }, fps: numberfps: 30 });
using narrow: CompiledScene<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
narrow
.
CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
    card: Card;
    caption: ChildField<typeof Caption, object>;
}>
at
(15).
FrameState<{ ...; }>.scene: {
    card: ResolvedActorOfInstance<Card> | undefined;
    caption: ResolvedActorOfInstance<Caption> | undefined;
}
scene
.card: ResolvedActorOfInstance<Card> | undefinedcard?.revealWidth: number | undefinedrevealWidth; // 150

A new stage size is a new build: the timeline is the same, and revealWidth takes its share of the stage the build was given.

In a Remotion app

Copy both files into a Remotion app and use composition.tsx as its entry point. The app needs React, Remotion, @motionactor/core, @motionactor/renderers, @motionactor/dom and @motionactor/remotion, and Tailwind for the class names in the view. The only inline styles are the resolved animation values.

If the app already calls registerRoot, drop the example’s call and add <Composition {...toRemotionComposition(card)} /> to the existing root.

Was this page helpful?