From figma
Translates Figma motion and animations into production-ready application code. Use when implementing motion from Figma designs, including keyframes, easing, and framework-specific motion libraries.
How this skill is triggered — by the user, by Claude, or both
Slash command
/figma:figma-implement-motionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill guides translation of Figma animations and transitions into runnable code (motion.dev, CSS keyframes, or framework-specific libraries).
This skill guides translation of Figma animations and transitions into runnable code (motion.dev, CSS keyframes, or framework-specific libraries).
Figma exposes motion through two tools:
get_motion_context — authoritative motion tool. Returns the complete animated-node inventory, precomputed code snippets (CSS @keyframes + motion.dev), fallback keyframe bindings when snippets are unavailable, and recursive timeline coordination hints. Source of truth for animation data and which node IDs animate.get_design_context — the design's structure: layout, sizing, assets, styling, Code Connect hints, screenshot context, and sometimes motion placement markers on animated elements (data-node-id, and on split nodes data-motion-keys / data-motion-wrapper-for / data-motion-transform-template). It may render an animated node as a plain element (div, p, span, etc.) or a motion element (motion.div); it does not inline the animation values.The two are linked by node id, and that's the whole workflow. get_motion_context tells you which nodes animate and gives the keyframe values, easing, timing, and snippets. get_design_context tells you what those nodes look like and where they sit. For every node in get_motion_context.nodes, find the matching data-node-id in design context and merge the motion into that structure — adding or wrapping a motion.{tag} when the structural element is plain. When design context has reused a Figma component, the motion node may also include fallbackNodeId; use it only as a fallback after trying the exact nodeId.
get_motion_context (snippets plus fallback keyframe tracks, including preset-authored motion resolved into those forms). Broader interactive variant flows may still need product-specific state handling in code.https://figma.com/design/:fileKey/:fileName?node-id=1-2 — extract fileKey (the segment after /design/) and nodeId (the value of the node-id query parameter, e.g. 42-15).For motion implementation, use both tools with distinct roles:
| Situation | Tool | Why |
|---|---|---|
| Understanding static structure, assets, styles, Code Connect, or visual layout | get_design_context | Gives the component/page code reference and asset URLs you need to place animated nodes correctly. |
| Fetching animation data for any node | get_motion_context | Purpose-built for motion and the source of truth for timing, easing, snippets, and keyframes. |
A node has motion markers (data-motion-keys, data-motion-wrapper-for) | Markers for split placement, get_motion_context for values | Split markers tell you which tracks go on which element; the keyframes/easing/timing and animated-node inventory come from get_motion_context. |
get_motion_context accepts recursive: true (capped at 500 nodes) when you need descendants' motion in one call.
get_design_context(fileKey=":fileKey", nodeId="<node-id>")
If get_design_context has already been called for this node, reuse that output. If not, call it normally now.
Use it as the structure of record — hierarchy, sizing, styling, assets, Code Connect hints, screenshot context, and any motion placement markers it happens to include (Step 3). The animated-node inventory and animation values come from get_motion_context (Step 2).
get_motion_context(fileKey=":fileKey", nodeId="<node-id>", recursive=true)
Response shape (one entry per animated node):
codeSnippets — pre-generated CSS @keyframes and motion.dev strings. Use these directly. Do not regenerate them from fallback track data.keyframeBindings — bound keyframe tracks, including preset-derived motion resolved into track data, included only as fallback data when both snippet formats are missing.fallbackNodeId — optional fallback id for matching componentized design context. If nodeId is an instance-qualified id such as I4005:6111;30:8005, D2R may render the reusable component body with the backing component id instead, such as 4002:3957. In that case, fallbackNodeId is the data-node-id to look for if exact nodeId lookup fails.Recursive responses may also include timelineCohorts: groups of animated node IDs that share a timeline root, duration, and playback mode (once, loop, or boomerang). Use cohorts to keep staggered or multi-node animations synchronized instead of inferring shared timing from sibling order.
Implementation details that matter for LLMs:
keyframeBindings, timelineDurationMs, motionSummary, and default transformOrigin may be stripped to keep the payload small. Missing keyframeBindings is not a signal that there is no animation.clientFrameworks; if the response only contains one snippet format, adapt that format to the user's stack rather than assuming the other format failed.get_motion_context.nodes, not from visible motion.* tags in the static JSX. Every returned node is animated. Match each motion node back to get_design_context by exact nodeId / data-node-id first. If and only if there is no exact match, try fallbackNodeId / data-node-id. Fall back to node name/type and screenshot position only after both ids fail.fallbackNodeId. fallbackNodeId points at the backing component id that D2R may emit inside a reusable component. It is shared by every instance of that component. If the exact nodeId exists in design context, apply motion there and ignore the fallback. This is critical for root-instance animation: one instance can rotate or move differently from another instance of the same component, and applying that motion to the shared component body would animate all instances incorrectly.data-node-id. The matching data-node-id is the structural anchor, not always the final DOM element that receives motion. Use the snippet shape and placement markers to decide whether motion goes on that exact element, a wrapper, an inner element, or an inlined SVG path. get_design_context may already emit motion.{tag} with values stripped, or it may emit a plain structural element (div, p, span, component root, etc.). If it is plain and the snippet targets the element itself, convert it to the appropriate motion.{tag} or add a motion wrapper while preserving the node's text, children, classes/styles, attributes, and data-node-id. Load references/examples-and-anti-examples.md to see examples of this merging step.4002:3957) while motion context reports live instance ids (I4005:6111;30:8005). In this case, use fallbackNodeId to find the component-body data-node-id, but keep the motion scoped to the rendered instance you are implementing. If there are multiple instances and only one has different root motion, exact id matching keeps that per-instance motion separate.data-motion-keys / data-motion-wrapper-for marker — see Handling interleaved transforms below.display: contents wrappers — unless the group itself animates. Layout-transparent group wrappers come through as contents (Tailwind contents), usually alongside a dead absolute/inset-[…] (those do nothing on a contents box). For a static group, keep display: contents and let the children position against the nearest real ancestor — converting the wrapper's inset into a positioned box reparents the children to a smaller box, so they render too small / shifted inward. For an animated group (the group node itself has motion), display: contents can't carry a transform — replace it with a real positioned wrapper and apply the group motion there. Load references/gotchas.md before implementing this case.get_motion_context is the complete animated-node inventory. Some animated nodes render as plain (non-motion) elements — component instance roots (plain positioning <div>), text (<p>), masks — that still carry a data-node-id. Walk every node in the motion response and apply its motion to the element with the matching data-node-id, wrapping or converting as needed. If an animated node has no element at all in the output (e.g. an animated mask flattened into a static mask-image), don't drop it silently — leave a // TODO: <nodeId> motion unsupported comment and call it out in your summary.get_motion_context.<path>. When get_motion_context targets a vector's path (PATH_TRIM, motion.path, stroke-dasharray) but design context renders it as an <img>, inline the SVG and apply the snippet to the <path>, keeping the layout wrapper. Load references/svg-and-path-motion.md for the full how-to for this case (motion.path, pathLength="1", wrapper+path layering, CSS path-trim).A node with both a static base transform and animated transforms is split across nested elements so the two compose correctly instead of fighting: an id-less motion.div carrying data-motion-wrapper-for="<nodeId>" (the OUTER wrapper) wraps a static-transform div (e.g. rotate-45 + hypot() sizing — or the wrapper itself carries data-motion-transform-template="<css>") which wraps the INNER node (data-node-id). Keep the wrapper > static-transform div > inner nesting — collapsing it breaks sizing and the base transform.
data-motion-keys. The wrapper's data-motion-keys (transform tracks — x/y/rotate/scaleX/scaleY/skewX) go on the OUTER wrapper; the inner element's data-motion-keys go on the INNER element.data-motion-transform-template. If the wrapper carries one, set transformTemplate={(_, generated) => "<css> " + generated} so the animated transform composes on top of that static layout transform.get_motion_context gives the node's absolute transform, which already includes whatever static base those divs apply. A rotate snippet of [45, 125, 125] over a rotate-45 base means the wrapper animates the offset [0, 80, 80] (= absolute − 45), not the absolute — else the 45° applies twice and the element sits at 90° at rest. Tracks with no static base (e.g. x/y starting at 0) pass through unchanged. See the interleaved-transform example.motion.* element that animates rotate, scale, or skew, verify it does not also rely on Tailwind layout transforms such as -translate-x-1/2 or -translate-y-1/2 for centering/positioning. Those utilities share the CSS transform property that Motion.dev writes inline, so Motion's transform can erase the layout translate. If both are needed, split the element into a static layout wrapper carrying the centering/positioning transform and an inner motion.* element carrying animated rotate/scale/opacity, or encode the layout offset in Motion itself (x: "-50%") and keep it present for every keyframe.motion/react — unless the codebase already uses another motion library (Framer Motion, React Spring, GSAP), in which case adapt the snippet to it. Load references/framework-recommendations.md when adapting to another stack or choosing a library.keyframeBindings or motionSummary as fallback data to construct equivalent motion.dev calls or CSS keyframes. Normally snippets are present; do this only when both snippet formats are genuinely missing.These are the general principles. Specific gotchas (rotation pivots, HOLD semantics, color interpolation, etc.) live in the categorized references. When a linked reference is mentioned in this skill text and the situation applies, load that file before continuing.
transformOrigin from codeSnippets — don't regenerate them from keyframeBindings when snippets exist (regenerating loses fidelity on custom bezier easings, spring approximations, and overshoot values). transformOrigin is per element: apply each scaling/rotating node's own — including nested scalers, not just the outer wrapper — or the element pivots from the default center and grows/spins from the wrong corner (see the per-element-transformOrigin example). But the snippet is one node's data, not a copy-paste template: when many nodes share it, factor it per Rule 7 instead of pasting the block N times.prefers-reduced-motion. Any motion added must soften or disable under @media (prefers-reduced-motion: reduce) — typically skip the animate (render the initial/resting state) or cut the duration to near-zero. This is an accessibility default, not an opt-in.Read it. get_design_context / get_motion_context return assets as URLs (/api/mcp/asset/...), often SVG. Reference the URL directly where the consumer fetches it (an <img src>, CSS background-image, an asset import), or curl one to inline its contents (e.g. inline the SVG and render via NSImage(data:) on SwiftUI). The important exception is path-level SVG motion: if the motion snippet targets a path inside an SVG asset, inline the SVG and animate the real path instead of leaving it behind an <img>. Don't download an asset and feed the file to the Read tool: SVG isn't a Read-able image format, so the read is rejected and wasted — and a file tool that doesn't detect SVG-as-image can stall the loop on it.variants object parameterized by the values that vary — render from a mapped array (items.map(...)), and pull repeated literals (durations, easing arrays, offsets) into named constants. The animation's values stay verbatim from the snippet (Rule 1); the code stays DRY. The same transition object pasted 15+ times (800 lines that should be 150) is a low-quality result — fidelity and maintainability are both graded.Rule 2 covers the general posture: prefer the user's existing stack. When none exists, defaults:
motion package). The tool returns motion.dev code directly — use it.@keyframes with animation shorthand, returned directly by the tool..animation(...) modifiers; translate from snippets, keyframeBindings, or motionSummary (get_motion_context does not emit SwiftUI code). Use only real SwiftUI APIs — there is no modifier that takes a Figma/CSS easing type directly, so load references/framework-recommendations.md, map the emitted easing to its SwiftUI equivalent, and verify rather than inventing a method. This path is evolving; confirm with the user if unsure.For established effect classes, prefer a library over hand-rolled CSS. Effects like glass/glassmorphism, confetti, particle systems, physics-based interactions, and scroll-linked motion have battle-tested library implementations that handle cross-browser quirks, accessibility, and performance far better than generated keyframes. Load references/framework-recommendations.md for the full library-by-effect-class table. Surface these as recommendations, not mandates — the user decides.
Load references/examples-and-anti-examples.md when you need worked examples or failure patterns. It covers the simple merge flow, plain text elements that need motion.* added, interleaved static+animated transforms, SVG path-level motion, and anti-examples for DOM rebuilding, node-id/position drift, and missing per-element transformOrigin.
Six deep dives, fetched on demand. General frontend concerns (performance, units, accessibility mechanics) are handled by the critical rules above — these references focus on Figma-specific signal only. If this skill names one of these files in an inline instruction, load that file before continuing with that part of the task.
transformOrigin.motion.path, pathLength="1", wrapper+path layering, CSS path-trim). Load when a vector's snippet targets the path, not a wrapper transform.npx claudepluginhub anthropics/claude-plugins-official --plugin figmaCreates smooth React/JavaScript animations with Motion/Framer Motion: motion components, variants, gestures (hover/tap/drag), layout/exit animations, springs, scroll effects. For interactive UIs, micro-interactions, transitions.
Motion design skill for defining and implementing UI animations, transitions, and micro-interactions using Framer Motion, CSS, and Tailwind. Creates reusable motion tokens and performs 60fps animations.
Provides reusable animation patterns for UI components—buttons, modals, lists, page transitions, scroll reveals—using motion-foundations tokens. Best when adding entrance/exit animations or scroll-linked effects.