Figma Motion → real code

Blinn MotionAnimate in Figma.
Ship it everywhere.

The runtime for Figma Motion. Export a timeline to a tinyMotionDoc, play it with a pure-JS engine — the same motion, frame for frame, on every platform. No video bake. No rebuild in CSS.

identical timing on
DOMCanvasReactVueSvelteAngularLitRN
card.motion60 fpsLIVE
0.00s
springcubic-bézierloop
The pipeline

From Figma timeline
to a tree of pure numbers

No black box in the middle. Your animation becomes an open document, then a deterministic render tree — the same maths everywhere it plays.

01
Figma Motion

Animate on the timeline

Keyframes, easing curves and springs live in node.animations — raw data, never rasterized.

02
@blinn-motion/figma-plugin

Export, don't bake

The plugin reads the timeline and emits a tiny MotionDoc — no PNGs, no video, just your motion.

03
@blinn-motion/core

sample(doc, t)

The pure render method walks the doc, solves every track at time t, returns a resolved tree of final numbers.

one resolved tree · 8 thin adapters
DOMCanvasReactVueSvelteAngularLitRNSee packages & labs
Platforms

One document.
Every stack, same timing.

Thin adapters paint the same resolved tree. Pick a package — open a live lab where one exists.

DOM

@blinn-motion/dom
full fidelity

Nested divs, gradients, SVG paths, masks and procedural shaders.

import { create } from "@blinn-motion/dom";
create(el, doc, { loop: true }).play();
Open live lab

Canvas

@blinn-motion/canvas
zero DOM

Immediate-mode 2D painter for the same resolved tree.

import { create } from "@blinn-motion/canvas";
create(el, doc, { loop: true }).play();
Package only

React

@blinn-motion/react
component + hook

Declarative component and hook — switch painter with one prop.

<BlinnMotion doc={doc} renderer="canvas" loop autoplay />
Open live lab

Vue

@blinn-motion/vue
component + composable

Vue 3 component and useBlinnMotion composable.

<BlinnMotion :doc="doc" renderer="dom" :loop="true" />
Open live lab

Svelte

@blinn-motion/svelte
use:action

Idiomatic use:blinnMotion action plus attach API.

<div use:blinnMotion={{ doc, renderer: "canvas" }} />
Open live lab

Angular

@blinn-motion/angular
standalone

Standalone <blinn-motion> with progress and frame outputs.

<blinn-motion [doc]="doc" renderer="dom" />
Open live lab

Lit

@blinn-motion/lit
custom element

Web component — drop into any host that supports custom elements.

<blinn-motion .doc=${doc} renderer="canvas"></blinn-motion>
Open live lab

React Native

@blinn-motion/react-native
native views

Same timing on native Views — no native module, no Skia dep.

<BlinnMotionView doc={doc} loop />
Package only
Live head-to-head

Same motion.
Pixel-matched.

Left: the public LottieFiles file, played by lottie-web. Right: the same geometry, keyframes and easings as a Blinn MotionDoc + SVG paint — check path-trim, polystars, ellipses, confetti dots. Zero restyle.

Lottie

lottie-web · SVG

LIVE
success.lottie.jsonLottieFiles · lf20_jbrw3hcz
Animation JSON
24.9 KB3.83 KB gzip
Player (gzip)
~73.2 KBlottie-web
Ship cost
~77.1 KBplayer + JSON gzip
Blinn

MotionDoc · exact SVG

LIVE
success.blinn.jsonextracted keyframes · same paths
Animation JSON
2.89 KB1.00 KB gzip · 8.6× smaller
Player (gzip)
~8.79 KBcore + paint
Ship cost
~9.8 KBplayer + JSON gzip · 7.9× lighter
Verdict

Same picture, smaller file.Animation JSON is 8.6× smaller; total ship cost ~7.9× lighter. Lottie carries AE shape trees + unused glyph outlines — Blinn keeps only the resolved motion.

Keyframes, cubic easings (0.333,0 → 0.667,1), check trim path, 4 polystars, 8 confetti dots and both green ellipses taken from the Lottie JSON. Text layer in the source has empty t:"" — matched. Player sizes: lottie-web ~75 KB gzip · Blinn core+DOM ~9 KB gzip.

Performance

Built lighter than Lottie
for product UI motion

Lottie is excellent for After Effects illustration. Blinn is the thin runtime forFigma Motion — same seekable timeline, a fraction of the player weight. See the live side-by-side in vs Lottie.

Blinn · core + DOM~9KBgzip · minify
lottie-web~75KBgzip · minify
~8× lighter player

Typical web path for UI motion. Light Lottie is still ~4× heavier (~38 KB gzip).

Blinn MotionLottieEdge
Player JS (gzip)~9 KBcore + dom~75 KBlottie-web full~8× smaller
Light player (gzip)~9 KBsame path~38 KBlottie_light~4× smaller
Runtime depsZeropure JS corePlayer runtimefull AE feature setNo bloat tax
Source pipelineFigma Motionproduct UI timelinesAfter EffectsBodymovin / LottieFilesDifferent jobs

Tiny install surface

Ship ~9 KB gzip for core + DOM instead of a 40–75 KB Lottie player. Product pages stay lean when motion is a polish layer, not the product.

CSS-native DOM path

The DOM adapter paints with transforms, opacity and SVG the browser already optimizes — no full scene graph re-parse every frame for simple UI motion.

Seek / scrub without re-authoring

sample(doc, t) is pure and deterministic. Scroll- and gesture-linked progress is a first-class API, not a bolted-on player control.

Diffable MotionDoc

Keyframe JSON stays small and reviewable in PRs. No opaque binary player state — engineers own the artifact.

Sizes measured from published package builds (Blinn core+dom minify+gzip) and public lottie-web bundle figures. Animation JSON size varies by scene — MotionDoc keyframes stay sparse; Lottie AE exports often denser. Different source tools, different jobs.

The render engine

One pure method does the work

@blinn-motion/core is time-based and DOM-free. The whole engine hangs off a single deterministic function — everything else is a thin painter.

core/sample.ts
// THE render method — pure, DOM-free
export function sample(doc: MotionDoc, t: number) return doc.layers.map((layer) =>
    resolve(layer, t)   // walk + sample every track
  );


// each track, eased + composed over the base
const v = ease(key.easing, local);
node[p] = op === "offset"
  ? base + lerp(a, b, v)   // additive
  : lerp(a, b, v);          // replace

// → resolved RenderNode tree (final numbers)
easing solvers
linearconstant rate
holdstep / no tween
cubicBezierNewton-Raphson
springdamped approx.
Time-based, in seconds

The core speaks one language: t in seconds. The playback clock is shared, so play / pause / seek / loop behave the same everywhere.

Progress-driven, too

Map any 0…1 signal — scroll, drag, state — with setProgress or sample(doc, progress × duration). No black-box player required.

Composes stacked tracks

Multiple tracks per property compose over the base value — set replaces, offset adds — so springs and curves layer cleanly.

Returns final numbers

Every transform, RGBA color and shape vertex comes out resolved. Adapters never re-interpret intent; they just paint.

Why Blinn Motion

Figma Motion, shipped as code

Keep motion where product teams already work — then resolve it to numbers and paint it the same way on every surface you ship to.

Figma is the source of truth

Select a Motion timeline, preview live, download MotionDoc — no rebuild in CSS, no After Effects detour.

Pure-JS render core

sample(doc, t) is DOM-free and unit-tested. Only adapters touch a platform — the engine runs anywhere JS does.

Springs & curves, solved

Linear, hold, cubic-bezier (Newton-Raphson) and damped springs — sampled per frame, not pre-baked.

MotionDoc you can own

Small, readable JSON. Inspectable, git-diffable, versionable — designed for product handoff, not a black box.

Clock or progress-driven

Play on a shared clock — or drive with setProgress(0…1) for scroll- and gesture-linked motion on every binding.

Built for product UI motion

Nested transforms, gradients, vectors, masks, path trim, shaders — real Figma frames, not only illustration loops.

Use it in code

Three lines to motion

Hand the engine a MotionDoc and a host. Play on a clock, or drive with setProgress(0…1) / a controlled progress prop.

install
npm i @blinn-motion/react

also: vue · svelte · angular · lit · dom · canvas · react-native

import { create } from "@blinn-motion/dom";
import doc from "./card.motion.json";

const player = create(
  document.getElementById("stage")!,
  doc,
  { loop: true }
);

player.play();
player.seek(0.8);          // seconds
player.setProgress(0.5);   // 0…1 — scroll / gesture
import { create } from "@blinn-motion/canvas";
import doc from "./card.motion.json";

const player = create(canvasHost, doc, { loop: true });
player.play();
// drive from scroll instead of the clock:
// player.setProgress(scrollY / range);
import { BlinnMotion } from "@blinn-motion/react";
import doc from "./card.motion.json";

// clock-driven
<BlinnMotion doc={doc} renderer="canvas" loop autoplay />

// progress-driven (scroll, drag, state)
<BlinnMotion doc={doc} progress={scrollP} />
import { BlinnMotion } from "@blinn-motion/vue";
import doc from "./card.motion.json";

// clock-driven
<BlinnMotion :doc="doc" renderer="dom" :loop="true" :autoplay="true" />

// progress-driven
// <BlinnMotion :doc="doc" :progress="scrollP" />
import { BlinnMotionView } from "@blinn-motion/react-native";
import doc from "./card.motion.json";

// clock
<BlinnMotionView doc={doc} loop autoplay />

// or progress-driven
<BlinnMotionView doc={doc} progress={gestureP} />
When motion lives in Figma

Ship the timeline.
Skip the detour.

Product teams already animate in Figma — then rebuild in CSS, export a video, or leave for another tool. Blinn is the runtime for motion that already lives in Figma.

Hand CSS / JSVideo / GIFBlinn Motion
Source of truthRebuilt from handoffBaked exportFigma Motion timeline
Timing & springsApproximate in CSS/JSLocked in pixelsSame keyframes & easings
InteractiveYes — if you code itPlay-only mediaPlay · seek · scrub · rate
PlatformsUsually web-onlyAnywhere as a fileDOM · Canvas · React · Vue · Svelte · Angular · Lit · RN
Git / PR reviewDiff the hand-written codeBinary blobDiffable MotionDoc JSON
Designer → engSpec + rebuildDrop a fileSame file ships

Blinn Motion is the runtime for Figma Motion — not a rebuild of another motion format. MIT-licensed and independent.

Open source · MIT

Animate in Figma.
Ship the same file.

Export from the Motion timeline, pick your stack in the Figma plugin, drop theMotionDoc into your app — DOM, React, Vue, Svelte, Angular, Lit, or native.

$ npm i @blinn-motion/react  # or vue · svelte · lit · dom