Reference
Architecture
How MotionKit works under the hood — plugin sandbox, UI thread, data persistence, rendering pipeline, and build system.
High-level architecture
MotionKit is a Figma plugin that runs in two separate execution contexts that communicate via postMessage:
- Plugin sandbox (
code.ts) — runs inside Figma's plugin engine with full access to the Figma document API. It reads and writes node properties, stores animation data, and performs rendering. - UI thread (
App.tsx+ React components) — a web page rendered inside Figma's plugin panel. It handles the timeline UI, easing editor, preset library, and the entire visual interface.
Communication model
The two threads exchange typed messages via figma.ui.postMessage and parent.postMessage. Every message has a type string and a typed payload. There are 55+ message types covering:
- Selection sync — when the user selects a Figma frame, the sandbox sends the full animation state to the UI.
- Property updates — the UI sends keyframe edits, easing changes, and timeline modifications to the sandbox.
- Render pipeline — the UI requests a render; the sandbox exports frames one at a time, sending each as a PNG buffer.
- Recording — the sandbox polls Figma for property changes every 150ms and sends diffs to the UI.
Data persistence
All animation data is stored directly on Figma nodes using the setPluginData API. This means:
- Animation data travels with the Figma file — no external database.
- Collaborators with the plugin installed see the same animation data.
- Data is stored as JSON strings under specific keys:
animationData,fbfData,morphData,textAnimData, andpresetData.
State management
The UI uses React with Zustand stores:
animationStore— master state for the currently selected frame's animation data (keyframes, properties, easing, loop mode, work area).uiStore— UI-only state (zoom level, scroll position, selected keyframes, active tool, panel visibility).fbfStore— frame-by-frame state (clips, gallery view mode, onion skin settings, current clip index).settingsStore— user preferences (theme, default FPS, shortcut overrides, auto-save).
Rendering pipeline
- User clicks Render — the UI sends a
RENDER_FRAMES(orRENDER_FBF) message with format, scale, and work area settings. - Frame loop — the sandbox iterates through each frame. For timeline mode it advances time; for FBF mode it steps through clips.
- Apply state — the sandbox sets all node properties to their interpolated values at the current time. Morph paths are applied first, then transforms, then fills/effects, then discrete properties.
- Export frame — the sandbox calls
node.exportAsync({ format: "PNG", ... })and sends the resulting buffer to the UI. - Encode — the UI encodes frames into the selected format (H.264 for MP4, LZW for GIF, ZIP for PNG sequence, canvas compositing for spritesheet).
- Download — the final encoded file is offered as a browser download.
Build system
| Tool | Role |
|---|---|
| Vite | Bundles the UI (React app) and the plugin sandbox (code.ts) separately |
| TypeScript | Strict mode across the entire codebase |
| React 18 | UI rendering with functional components and hooks |
| Zustand | Lightweight state management (no Redux boilerplate) |
| Tailwind CSS | Utility-first styling for the plugin UI |
File structure
src/
├── plugin/
│ └── code.ts # Plugin sandbox (7,700+ lines)
├── shared/
│ ├── types.ts # Shared type definitions
│ ├── constants.ts # Shared constants (FPS, colors)
│ ├── messages.ts # Message type definitions
│ ├── easingPresets.ts # 16 easing presets with bezier values
│ ├── transformUtils.ts # Matrix math, anchor transforms
│ ├── morphUtils.ts # Vector path interpolation
│ ├── fbfUtils.ts # Frame-by-frame clip utilities
│ ├── timelineRows.ts # Property grouping definitions
│ └── statusMessages.ts # User-facing status strings
└── ui/
├── App.tsx # Root component
├── main.tsx # Entry point
├── components/ # 40+ UI components
├── hooks/ # Custom React hooks
├── store/ # Zustand stores
└── utils/ # UI utilitiesKey design decisions
- No server dependency — everything runs client-side. No API keys, no cloud rendering, no internet required after install.
- Non-destructive — animation data is metadata attached to nodes. Removing the plugin leaves your design untouched.
- Hierarchical property detection — when recording, MotionKit filters out parent-caused changes (e.g., moving a group shouldn't record position changes on every child).
- Baseline restoration — after rendering or previewing, all nodes are restored to their state at time 0.