Motion System: Figma
Category: Design Tool Direct manipulation as the motion system — the cursor is the animation.
Motion Philosophy
Figma's motion philosophy begins with a fundamental insight: design tools are not applications you use, they are surfaces you inhabit. The user is not navigating to content — they are authoring it. In this context, the traditional model of "UI transitions" breaks down almost entirely. When a designer drags a frame, the frame moves. When they resize a component, it resizes. There is no animation layer mediating between the user's hand and the result; the cursor is the animation. This is the principle of direct manipulation, and in Figma it is applied with unusual rigor.
The motion system is therefore split into two distinct categories that rarely intersect. The first is direct manipulation: anything that tracks the user's cursor or input in real time operates at 0ms with no easing. Frame repositioning, node dragging, handle pulls, canvas pan and zoom — all of these follow the cursor exactly. Introducing easing to cursor-tracked elements would be a fundamental design error; it would make the tool feel like it is lagging behind the designer's intent. The second category is UI chrome: everything that is not the canvas itself. Panels sliding in, modals opening, dropdowns appearing, context menus dismissing. These follow conventional fast-transition principles at 100–200ms.
The interesting tension in Figma's motion system is at the boundary between canvas and chrome. Panel open/close uses spring physics because panels are adjacent to the canvas — the designer is extending the workspace, and the spring communicates the physical expansion of their environment. Resize handles use an elastic feel on release because the designer has been pulling on something, and springs naturally communicate the release of tension. These are the places where Figma's motion becomes expressive: not as decoration, but as physical feedback for physical-feeling interactions.
Duration Scale
| Token | Value | Use |
|---|---|---|
| duration-direct | 0ms | All cursor-tracked direct manipulation — dragging, resizing, canvas pan/zoom |
| duration-snap | 50ms | Snap indicators, alignment guides appearing, smart selection highlights |
| duration-fast | 100ms | Icon state changes, toggle switches, property panel value updates |
| duration-chrome | 150ms | Toolbar popovers, color picker open, small UI overlays |
| duration-panel | 200ms | Left panel open/close (layers, assets), right panel transitions, plugin panels |
| duration-modal | 200ms | Export dialog, share modal, prototype preview |
| duration-canvas | 300ms | "Fit to screen" zoom, "zoom to selection" — camera moves on the canvas |
Easing
| Token | Curve | Use |
|---|---|---|
| ease-out | cubic-bezier(0, 0, 0.2, 1) | Chrome elements entering — clean deceleration |
| ease-in | cubic-bezier(0.4, 0, 1, 1) | Chrome elements exiting |
| ease-in-out | cubic-bezier(0.4, 0, 0.2, 1) | Canvas camera transitions (zoom to fit, zoom to selection) |
| ease-panel | cubic-bezier(0.16, 1, 0.3, 1) | Panel edges entering — aggressive deceleration for the workspace-extension feel |
| ease-linear | cubic-bezier(0, 0, 1, 1) | Progress bars, loading bars, shimmer |
Direct manipulation uses no easing — frames, nodes, and handles follow the pointer exactly. Any easing applied to cursor-tracked elements would be a bug, not a feature.
Spring Configs (Framer Motion)
Springs are used for panel behaviors and interactive handles, not for general UI chrome.
- Left/right panel open: stiffness: 320, damping: 30, mass: 1 — slight overshoot, communicates workspace expanding
- Plugin panel: stiffness: 280, damping: 28, mass: 1 — slightly softer, plugins feel like guest surfaces
- Resize handle release (elastic snap): stiffness: 600, damping: 22, mass: 0.5 — elastic snap when releasing a constrained resize, brief overshoot, communicates the release of tension
- Context menu (right-click): stiffness: 500, damping: 40, mass: 0.7 — near-instant, very slight scale overshoot
- Prototype preview frame enter: stiffness: 240, damping: 28, mass: 1 — gentle bounce as prototype preview window appears
Stagger Patterns
- Layer panel items (initial load): 6ms between items, capped at 15 items — barely perceptible, prevents simultaneous flash
- Asset search results: 12ms between each component card
- Plugin grid in Community: 20ms between each plugin card, left to right, top to bottom
- Toolbar icon group (on first launch): 30ms between icon groups (not individual icons) — a single graceful settle
- Export format options: no stagger — appear simultaneously
Stagger is never applied during real-time collaboration events (another user adding frames, cursor presence appearing). Remote events animate in individually and instantly.
Enter / Exit Patterns
Direct Manipulation (canvas elements — the most important pattern)
drag start: 0ms — element follows cursor position with no transition
drag end (release): element snaps to final position at 0ms if no physics trigger. If snapping to a grid or guide: translateX/Y to snap position at duration-snap (50ms), ease: ease-out
resize: follows cursor at 0ms. On constraint release: spring Resize handle release config
Panel Open/Close (layers panel, assets panel, design panel)
enter: translateX -100%→0 (left panels), translateX 100%→0 (right panels), spring: Panel open config
exit: translateX 0→-100% or 0→100%, duration: duration-panel (200ms) × 0.7 = 140ms, ease: ease-in
canvas: simultaneously resizes to fill new available space, duration: same as panel transition
Context Menu (right-click menu)
enter: opacity 0→1, scale 0.97→1 (transform-origin: click point), spring: Context menu config
exit: opacity 1→0, scale 1→0.97, duration: duration-fast (100ms), ease: ease-in
Popover / Color Picker
enter: opacity 0→1, scale 0.98→1, translateY 4px→0, duration: duration-chrome (150ms), ease: ease-out
exit: opacity 1→0, duration: duration-fast (100ms), ease: ease-in
Modal (export, share)
enter: opacity 0→1, scale 0.97→1, duration: duration-modal (200ms), ease: ease-out
backdrop: opacity 0→0.5, duration: duration-modal (200ms), ease: ease-out
exit: opacity 1→0, duration: duration-fast (100ms), ease: ease-in
Canvas Camera (zoom to fit, zoom to selection, cmd+0)
viewBox animates: current bounds → target bounds, duration: duration-canvas (300ms), ease: ease-in-out
simultaneously: zoom level counter in toolbar crossfades to new value at duration-fast (100ms)
Multiplayer cursor (collaborator cursor appearing)
enter: opacity 0→1 with name tag, duration: duration-fast (100ms), ease: ease-out
movement: follows real-time position at 0ms — no interpolation, no easing on collaborator cursors
exit (user leaves or idles): opacity 1→0, duration: duration-chrome (150ms), ease: ease-in
Interaction States
- Hover (canvas elements): Selection handles appear at duration-snap (50ms) ease-out. Hover outline appears at 0ms — it is a cursor state, not an animation. Component description tooltip appears at duration-chrome (150ms) with a 600ms delay (prevents tooltip spam during canvas exploration).
- Hover (chrome): Background fill at duration-fast (100ms) ease-out. Icon color transitions at duration-fast (100ms).
- Press/Active (toolbar): Scale 0.92 on toolbar icon at 80ms ease-out, returns to 1.0 on release via Resize handle spring at low stiffness (200, damping 25).
- Focus (property inputs): Input border highlights at 0ms — inputs in the right panel must respond at typing speed. No animation on focus.
- Loading (file open, auto-save): Progress bar at the top of the window, linear easing, width animates from 0 to 100% over estimated load time. On save: brief checkmark icon swap at 0ms, then fade out at duration-fast (100ms).
- Skeleton (file thumbnails in home screen): Shimmer at 1400ms ease-in-out infinite.
- Reduced motion: All panel springs snap to end position. Canvas camera transitions become instant (0ms). Multiplayer cursors appear/disappear at 0ms. Resize handle elastic removed — snaps directly. Context menu appears at 0ms. Chrome transitions become opacity fades at 80ms.
Rules
- The cursor is the animation. Any element the user is directly manipulating must follow the cursor at exactly 0ms with no easing. This is a categorical rule with no exceptions — any latency or easing on cursor-tracked elements makes the tool feel broken.
- Springs are earned. Only elements at the boundary of direct manipulation — panels extending the workspace, handles releasing tension — use spring physics. General UI chrome uses CSS transitions.
- Never animate the canvas during user input. Zoom transitions, fit-to-screen, and frame-highlight animations must only play when the user is not actively dragging, resizing, or typing.
- Always implement
prefers-reduced-motion. Panel springs snap to position, canvas transitions are instant, prototype preview enters at 0ms. The tool must remain fully functional — and for power users, the instant behavior may actually be preferable in normal operation too.