Import from After Effects
remotion-ui import-ae converts a Bodymovin / Lottie JSON export into editable Remotion TSX built on ae-motion: named layer components, keyframes as data, and a composition with the right size, fps and length.
@remotion/lottie plays a Lottie file back. import-ae turns it into source: React components you can read, edit, parametrize and retime, built on the ae-motion primitives. Keyframes become ae-motion tracks with AE's own speed/influence easing, layers become named components, and nothing runs lottie-web at render time.
npx remotion-ui@latest import-ae hero.jsonInside a RemotionUI project this writes src/compositions/hero/index.tsx (plus one file per precomp), registers the composition in Root.tsx with the export's width, height, fps and duration, and installs the ae-import runtime (which pulls in ae-motion, bezier-path and shape-ops).
| Flag | Effect |
|---|---|
-o, --out <dir> | Output directory. Default: <compositions alias>/<name>, or ./<name> outside a project. |
-n, --name <Name> | Component and composition id (PascalCase). Default: the export's name, else the file name. |
--skip-unsupported | Generate even when the file uses unsupported features. They are listed at the top of the generated file and printed. |
-f, --force | Overwrite files from an earlier import. |
--no-register | Leave Root.tsx alone. |
--no-install | Do not install the ae-import runtime. |
--json | Machine-readable result, including every finding with its JSON path. |
What you get
From the lottie-docs bouncing ball, lightly abridged:
/** "Layer" — shape layer, frames 0–120. */
const layerLayer = shapeLayer({
name: "Layer",
inPoint: 0,
outPoint: 120,
contents: [
group(
"Ellipse Group",
[ellipse({ position: [204, 169], size: [153, 153] }), fill({ color: "#b53046" })],
{
anchor: [204, 169],
position: [
{ frame: 0, value: [235, 106], easeOut: { speed: 0, influence: 0.333 } },
{ frame: 60, value: [265, 441], easeIn: { speed: 0, influence: 0.001 } },
{ frame: 120, value: [235, 106], easeIn: { speed: 0, influence: 0.333 } },
],
},
),
],
});
export const Layer = () => <AeLayer layer={layerLayer} />;
export const BouncyBall = () => (
<AeComposition width={512} height={512} fps={60}>
<Layer />
</AeComposition>
);- Keyframes are data. Frames are composition frames;
speedis units per second andinfluence0–1, exactly what AE's Keyframe Velocity dialog shows. Scale is a factor (1= 100%) and opacity 0–1. Change a key, re-render. - Layers are components, listed bottom of the AE timeline first, so later children draw on top. Reorder, wrap or delete them like any JSX.
- Parenting is a reference:
parent: rigLayer. Null layers are plain data. - Every value is
Animatable: swap a track for a number or an expression({ frame, time, fps }) => valueto parametrize it from props.
Supported
| Area | Imported |
|---|---|
| Layers | Shape, solid, null, precomp (clipped to its size, as lottie-web does) |
| Timing | In/out points, precomp start time, time stretch, time remap, the export's first frame (ip) |
| Transform | Anchor, position (spatial bézier paths, separate dimensions), scale, rotation, opacity, skew, parenting |
| Shapes | Group, path (incl. animated paths), rectangle, ellipse, star, polygon |
| Styles | Fill, stroke (caps, joins, miter limit, dashes), gradient fill and stroke (linear, radial with highlight, opacity stops) |
| Modifiers | Trim Paths (simultaneous and individual, offset, wrap-around), Repeater, Offset Paths, Zig Zag, Pucker & Bloat |
| Easing | Bézier (exact speed/influence mapping), linear, hold, per-dimension curves (split into separate tracks) |
Stacking follows AE and lottie-web: a style paints every path above it in its group, nested groups included; a modifier reshapes every path above it, and every style drawing that path sees the result; a repeater copies everything above it, styles included.
Not supported — fails loudly
By default import-ae refuses to write anything when the file uses a feature it cannot carry over, and prints each one with the layer, group and JSON path. --skip-unsupported generates the rest and lists what was left out in the generated file's header.
- Text layers, image / video / footage layers, cameras, lights, audio (an unsupported layer is kept as a null so layers parented to it still move)
- Masks, track mattes (the matte source is kept but hidden), layer effects, blend modes
- Expressions (the pre-expression value is used)
- 3D layers and auto-orient
- Round Corners, Merge Paths, Twist
- Wiggle Paths: Bodymovin does not export it, so there is nothing to import. Add
shape-ops'wiggleoperator by hand.
Hidden layers and hidden items import with hidden: true, so you can switch them back on.
Fidelity
Measured by rendering each fixture with @remotion/lottie and with the generated TSX at the same frames (first, a keyframe, both quarters, middle, last), then scoring PSNR, SSIM and the share of pixels differing by more than 32/255. The bench, 26 fixtures from Airbnb's lottie-android tests (Apache-2.0) and the lottie-docs examples (CC BY 4.0) plus animated variants of them, lives in apps/web/showcase/import-ae-bench.
- 25 of 26 fixtures match lottie-web at ≥ 41.9 dB minimum PSNR and ≤ 0.17% mismatched pixels on every sampled frame, including Airbnb's Lottie logo animations and stacked, animated repeaters. The remaining difference is anti-aliasing along edges.
- An animated Zig Zag fixture scores 27.7 dB on frame 0 only, where the reference is wrong: lottie-web keeps a stale zig-zag when its size is 0. Its other frames score ≥ 58.7 dB.
- The reference is always lottie-web after it has drawn another frame, never swapped per fixture. The bench README documents the two lottie-web bugs it hit, with evidence.
lottie-web is the reference
lottie-web is what designers preview in (LottieFiles, the Bodymovin preview), so import-ae reproduces its behaviour, quirks included:
- Repeaters keep their originals. When a repeater's fill or stroke sits below it, those styles also paint the original, un-repeated shapes, so a non-zero offset shows the original alongside the copies.
- Repeater position and rotation compose as lottie-web does: each copy is rotated and scaled about the anchor, then moved by its position steps.
- Spatial speed is measured along the motion path, as AE's speed graph measures it, so a position that loops back to its start keeps its ease.
- Time remap landing exactly on a precomp layer's out point shows the frame before.
Known differences
- Offset Paths uses a port of lottie-web's own offsetter (per-segment bézier offset, intersection pruning, its round-join arcs) rather than
shape-ops' polyline offsetter, so insets and joins match the preview. - Colours floor to 0–255 and gradient offsets round to whole percents, both as lottie-web does.