Back Original

Markdy: Like Mermaid Diagrams, but for Motion

Key Features

📦

Zero Dependencies

Pure TypeScript parser — no DOM, no runtime deps.

🌐

Web-Native Renderer

Web Animations API + CSS transforms. No Canvas, no GSAP.

🤖

AI-Agent Friendly

Structured DSL that LLMs can generate, validate, and iterate.

🧩

Framework Agnostic

Works anywhere. Astro island component included.

Editor

Ctrl+Space autocomplete · Cmd+Enter run

Every animation starts by defining the canvas. This must be the first line in your .markdy file.

scene width=800 height=400 fps=30 bg=#fafafa
PropertyDefaultDescription
width800Canvas width in pixels
height400Canvas height in pixels
fps30Frame rate for the rendering engine
bgwhiteAny CSS color value (e.g. #1a1a2e)
durationautoOverride total length in seconds

Actors are the objects in your scene. Place them with at (x, y) and add optional modifiers.

actor a = figure(#c68642, m, 😎) at (100, 200) scale 1.2
actor b = text("Hello World") at (50, 50) size 24
actor c = box() at (300, 200)
TypeArgumentsDescription
text"quoted string"Plain text label. Customise with size.
box100x100 solid box. Great for prototyping.
figureskinColor, gender, faceStick figure with articulatable limbs.
spriteasset nameRenders an image or icon asset.

Modifiers: opacity (0–1), scale, rotate, size (text only), z (layer order).

Schedule actions on a timeline with @time: actor.action(). Time is in decimal seconds.

@0.5: a.enter(from=left, dur=0.8)
@1.5: a.move(to=(400, 200), dur=1.0, ease=out)
@3.0: a.fade_out(dur=0.5)
ActionParametersWhat it does
enterfrom, durSlides in from canvas edge
moveto, dur, easeTranslates to target coordinates
fade_in / fade_outdurAnimates opacity
scale / rotateto, durTransforms the actor

Easing: linear, in, out, inout

Make actors talk with comic speech bubbles and add dynamic effects like shaking.

@1.0: a.say("Hello! 👋", dur=1.5)
@2.5: a.shake(intensity=8, dur=0.4)
ActionParametersWhat it does
say"text", durShows an anchored speech bubble
shakeintensity, durOscillates on the X axis
throwasset, to, durLaunches a projectile between actors

figure actors have articulated limbs you can individually control for expressive animations.

@1.0: a.face("😵")
@1.5: a.rotate_part(part=arm_right, to=130, dur=0.4)
@2.0: a.punch(side=right, dur=0.2)
@2.5: a.kick(side=left, dur=0.3)
ActionParameters
face"emoji" — instant expression swap
rotate_partpart, to (degrees), dur
punch / kickside (left|right), dur

Body parts: head, face, body, arm_left, arm_right, leg_left, leg_right

Built-in gestures save you from chaining rotate_part calls. pose sets multiple parts at once.

@1.0: a.wave(side=right, dur=0.8)
@2.0: a.nod(dur=0.4)
@3.0: a.pose(arm_left=70, arm_right=-70, dur=0.4)
@4.0: a.jump(height=25, dur=0.5)
@4.5: a.bounce(intensity=12, count=3, dur=0.5)
ActionParametersWhat it does
waveside, durWave gesture — arm up, oscillate, return
noddurHead nod — down and up twice
posearm_left, arm_right, leg_left, leg_right, head, body, durSet multiple parts at once
jumpheight, durJump with squash/stretch
bounceintensity, count, durDiminishing vertical bounce

Load external images and vector icons as assets, then render them as sprite actors.

# Declare assets first
asset cat = image("https://example.com/cat.gif")
asset fire = icon("lucide:flame")

# Render them as actors
actor meme = sprite(cat) at (400, 200) scale 0.5
actor icon = sprite(fire) at (100, 100) scale 2.0
image("url")Loads images, GIFs, photos — any URL or local path.
icon("set:name")Loads SVG icons from Iconify. Never blurs when scaled.

Avoid repetition with var for values and def for reusable actor templates.

var accent_skin = #fad4c0

def char(skin, face) {
  figure($${skin}, f, $${face})
}

actor lily = char($${accent_skin}, 😊) at (100, 200)

var substitutes values (robust for #hex colors). def creates actor factory templates — build character systems without any JavaScript.

Package multi-step choreographies into reusable seq blocks. The ultimate power move.

seq wave(arm, angle) {
  @+0.0: $.rotate_part(part=$${arm}, to=$${angle}, dur=0.3)
  @+0.3: $.rotate_part(part=$${arm}, to=25, dur=0.3)
}

@2.0: lily.play(wave, arm=arm_left, angle=-80)

$ refers to the calling actor. Times with @+ are relative offsets from when .play() is triggered.

How it works

  1. Point your AI to the docs

    Share the AGENT.md — a single all-in-one reference with grammar, actions, patterns and examples.

  2. Describe your scene in plain English

    Tell the AI what you want to visualize — characters, actions, emotions, timing.

  3. Get valid MarkdyScript back

    The AI generates syntactically correct code. Paste it into the playground or your app.

Example prompt

Use https://github.com/HoangYell/markdy-com/blob/main/docs/AGENT.md as a complete reference (grammar, actions, patterns, examples), then write a Markdy scene:

The AI reads the docs, understands the grammar, and outputs a complete .markdy scene with actors, timeline events, and expressions — ready to render.