K

Explore

MCP

Build

/Lottie Generator

Lottie Generator: Prompt to Valid Bodymovin JSON With Claude Code

Build an agent that turns prompts into valid Bodymovin JSON — shape layers, keyframed transforms, bezier paths — with a live preview and scrubbable timeline. Deploy in 60 seconds with 21st SDK.

Serafim Korablev
Serafim Korablev
@serafimcloud

What it does

You type a prompt. The agent writes a valid Bodymovin JSON — shape layers, keyframed transforms, bezier paths. You get a live preview with a scrubbable timeline, hover-scrub, copy JSON, one-click download. A thinking banner shows live phase hints while the agent writes.

No DB. No auth. State in localStorage. The whole thing runs in 60 seconds after you clone.

How it works

The agent is a single tool: render_lottie. It takes a prompt, returns {animation, name, description}animation being a zod-validated Bodymovin JSON. That's it. One tool, one responsibility.

The app around it is Next.js 16 + Tailwind + lottie-react for playback, plus a custom canvas timeline for the scrub interaction. Shipping state is in localStorage — no persistence layer, no auth flow, nothing to deploy besides the agent itself.

The trick: handing Claude the schema inline

The system prompt is 40% Lottie cheatsheet. Bodymovin is a big spec and Claude's default knowledge of it is incomplete — but hand it the schema rules inline and it writes valid JSON first-shot.

The important pieces:

  • ty: 4 for shape layers
  • ks transforms with a: 0 (static) or a: 1 (animated) on each property
  • bezier paths for custom shapes
  • fl for fill, st for stroke
  • colors as [r, g, b, a] in 0..1 space, not 0..255
  • ip / op frame windows for when a layer is in/out

That's the entire trick. No embeddings, no RAG, no vector DB. Just a well-written system prompt and zod validation on the way out. Bodymovin v5.7+ compatible output, first-shot, most of the time.

Run it in 60 seconds

Clone the template as a standalone repo and deploy:

npx degit 21st-dev/21st-sdk-examples/lottie-generator my-lottie-agent
cd my-lottie-agent
npm install
npx @21st-sdk/cli deploy
npm run dev

That's the full setup. The cli deploy command pushes the agent to the 21st runtime; npm run dev starts the Next.js app that talks to it.

Customize it

Fork points:

  • System prompt — swap the Lottie cheatsheet for a different spec (e.g. SVG animations, Rive JSON).
  • Validation schema — the zod schema in the tool catches malformed output before it gets rendered. Tighten or loosen as needed.
  • Timeline UI — the canvas scrubber is ~150 lines, easy to style or replace with a library.
  • Export options — right now it's copy JSON + download. Add dotLottie export, PNG sequence, or video render.

What's next

Rive support is the obvious next step (asked for it in the replies). The same pattern — spec-rich system prompt + schema-validated tool — should work for any JSON-based animation format.


Source: 21st-dev/21st-sdk-examples/lottie-generator SDK: 21st SDK

Agent Template

The full agent — one zod-validated tool plus the Lottie-cheatsheet system prompt. Fork and replace the schema for a different JSON format.

agents/lottie-generator.tsView on GitHub →
import { agent, tool } from "@21st-sdk/agent"
import { z } from "zod"

const lottieSchema = z.object({
  v: z.string(),
  ip: z.number(),
  op: z.number(),
  w: z.number(),
  h: z.number(),
  fr: z.number(),
  layers: z.array(z.any()),
  assets: z.array(z.any()).optional(),
}).passthrough()

export default agent({
  model: "claude-sonnet-4-6",
  runtime: "claude-code",
  permissionMode: "bypassPermissions",
  maxTurns: 10,

  systemPrompt: `You generate valid Bodymovin JSON (Lottie v5.7+) for short animations.

SCHEMA RULES:
- Shape layers use ty: 4, with ks (transform) containing a, p, s, r, o.
- Each transform property has a: 0 (static value in k) or a: 1 (keyframed, k is array of {t, s, e}).
- Colors are [r, g, b, a] in 0..1, not 0..255.
- Bezier paths: {i: [...], o: [...], v: [...], c: boolean}.
- Fill: {ty: "fl", c: {a: 0, k: [r,g,b,a]}, o: {a: 0, k: 100}}.
- Stroke: {ty: "st", c: ..., o: ..., w: {a: 0, k: px}}.
- ip / op define when a layer is in/out (frames).
- Top-level: v (version), ip (0), op (total frames), w, h, fr (framerate), layers [].

OUTPUT:
Call render_lottie with a complete JSON that plays standalone.`,

  tools: {
    render_lottie: tool({
      description: "Render the generated Lottie animation in the preview.",
      inputSchema: z.object({
        animation: lottieSchema,
        name: z.string(),
        description: z.string(),
      }),
      execute: async ({ animation, name, description }) => {
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify({ animation, name, description }),
            },
          ],
        }
      },
    }),
  },
})