Motion design tokens and animation timing on design workspace

Motion Tokens Explained: Bridge Figma Smart Animate to CSS and Framer Motion

Motion tokens explained bridge the gap between what designers specify in Figma Smart Animate and what developers implement in CSS transitions, keyframes, and Framer Motion. Without a shared vocabulary for duration, easing, delay, and stagger, handoff becomes “make it feel snappy” — and AI coding agents invent random 200ms ease-in-out on every hover state. This guide defines motion tokens, maps Figma Smart Animate properties to code, and ships copy-paste examples for CSS custom properties and Framer Motion variants. Updated November 2026.

Figma Smart Animate prototype settings mapped to motion design tokens for developer handoff
Smart Animate encodes motion decisions in Figma — motion tokens carry those decisions into production code without guesswork.

Foundation: Figma Smart Animate micro-interactions, motion handoff tools, and Figma variables to code. For building UI that animates correctly, see AI prompts for UI generation and accessible UI prompts — motion must respect prefers-reduced-motion.

Table of contents

  1. What are motion tokens?
  2. Figma Smart Animate to tokens
  3. Duration and easing examples
  4. CSS implementation
  5. Framer Motion implementation
  6. Prompts for AI builders
  7. FAQ

What are motion tokens?

Motion tokens are named design decisions for animation — parallel to color and spacing tokens. Instead of scattering transition: 300ms cubic-bezier(0.4, 0, 0.2, 1) across components, you define:

  • Duration tokens — instant, fast, normal, slow, deliberate
  • Easing tokens — ease-out for entrances, ease-in for exits, spring for playful UI
  • Delay tokens — none, short, stagger step
  • Property tokens — which attributes animate (opacity, transform, layout)

Tokens create consistency: every modal enters in 250ms with the same curve; list items stagger by 50ms; reduced-motion users get instant state changes. They also give AI agents a finite vocabulary — “use motion.duration.normal and motion.easing.emphasized” beats “animate nicely.”

Token nameDurationTypical use
motion.duration.instant0msReduced motion fallback; toggles
motion.duration.fast100–150msHover, focus ring, micro-feedback
motion.duration.normal200–250msDropdowns, tooltips, small panels
motion.duration.slow350–400msModals, page sections, cards
motion.duration.deliberate500–600msHero reveals, onboarding emphasis

Align motion tokens with your Figma variables handoff — many teams store durations as float variables and easings as string variables in Figma Dev Mode.

Mapping Figma Smart Animate to motion tokens

Smart Animate interpolates between two frames when layer properties change: position, size, rotation, opacity, fill, corner radius, and more. The prototype panel sets transition type (ease in/out, linear, spring-ish curves via custom bezier), duration in milliseconds, and delay.

Handoff workflow:

  1. Designer names layers consistently between states (Button/Default, Button/Hover)
  2. Prototype connection documents duration + easing per interaction
  3. Developer extracts values into motion token table (below)
  4. Implement with CSS or Framer Motion using token names only
  5. QA compares screen recording to Figma prototype side by side

Read Smart Animate micro-interactions for button, tab, and card patterns. Use motion handoff tools (Anima, Locofy, Figma inspect) to export initial values — then normalize to your token scale because exported code rarely matches your stack.

Smart Animate behaviorCSS approachFramer Motion approach
Opacity fade between statestransition: opacity var(–motion-duration-normal)animate={{ opacity: 1 }} transition={{ duration: 0.25 }}
Position slide (x/y)transform + transition; prefer transform over top/leftanimate={{ x: 0 }} from {{ x: 20 }}
Size morph (width/height)transform: scale() when possible; else width with layout cautionlayout prop or animate width (triggers layout)
Corner radius changetransition: border-radiusanimate={{ borderRadius: 12 }}
Staggered list appearanimation-delay: calc(var(–stagger) * index)staggerChildren in transition variant
Spring feelcubic-bezier approximations only — true spring needs JStransition: { type: “spring”, stiffness, damping }

Duration and easing token examples

Standard easing tokens (Material-influenced, widely understood):

TokenValueUse when
motion.easing.standardcubic-bezier(0.4, 0, 0.2, 1)Default state changes
motion.easing.emphasizedcubic-bezier(0.2, 0, 0, 1)Elements entering screen
motion.easing.emphasized-deceleratecubic-bezier(0, 0, 0, 1)Exits slowing to rest
motion.easing.emphasized-acceleratecubic-bezier(0.3, 0, 1, 1)Exits leaving screen
motion.easing.linearlinearProgress bars, marquees only

Framer Motion spring tokens (for Smart Animate “spring” feel):

  • motion.spring.snappy — stiffness 400, damping 30
  • motion.spring.gentle — stiffness 120, damping 20
  • motion.spring.bouncy — stiffness 300, damping 15

Document springs separately from CSS bezier — they are not interchangeable. When handoff says “spring,” implement in Framer Motion or React Spring; do not approximate with a 300ms ease-out unless designer approves.

Code editor showing Framer Motion variants and CSS custom properties for motion design tokens
Keep motion token source of truth in one file — tailwind.config theme extension, CSS :root, or motion.ts — and import everywhere.

CSS implementation with custom properties

Add to globals.css or tokens/motion.css:

:root {
  --motion-duration-instant: 0ms;
  --motion-duration-fast: 120ms;
  --motion-duration-normal: 240ms;
  --motion-duration-slow: 360ms;
  --motion-duration-deliberate: 500ms;

  --motion-easing-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --motion-easing-emphasized: cubic-bezier(0.2, 0, 0, 1);
  --motion-easing-enter: cubic-bezier(0, 0, 0.2, 1);
  --motion-easing-exit: cubic-bezier(0.4, 0, 1, 1);

  --motion-transition-fade: opacity var(--motion-duration-normal) var(--motion-easing-standard);
  --motion-transition-transform: transform var(--motion-duration-normal) var(--motion-easing-emphasized);
  --motion-stagger-step: 50ms;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --motion-duration-fast: 0ms;
    --motion-duration-normal: 0ms;
    --motion-duration-slow: 0ms;
    --motion-duration-deliberate: 0ms;
  }
}

.card {
  transition: var(--motion-transition-transform), var(--motion-transition-fade);
}

.card:hover {
  transform: translateY(-4px);
}

.list-item {
  animation: fadeUp var(--motion-duration-normal) var(--motion-easing-enter) backwards;
  animation-delay: calc(var(--index, 0) * var(--motion-stagger-step));
}

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

Tailwind extension — map tokens in tailwind.config.ts:

theme: {
  extend: {
    transitionDuration: {
      fast: "var(--motion-duration-fast)",
      normal: "var(--motion-duration-normal)",
      slow: "var(--motion-duration-slow)",
    },
    transitionTimingFunction: {
      emphasized: "var(--motion-easing-emphasized)",
      standard: "var(--motion-easing-standard)",
    },
  },
}

Then classes like transition-transform duration-normal ease-emphasized stay on-token. Prompt AI: “Use only motion tokens from tailwind.config; no arbitrary duration-[237ms].”

Framer Motion implementation

Centralize in lib/motion.ts:

export const durations = {
  fast: 0.12,
  normal: 0.24,
  slow: 0.36,
  deliberate: 0.5,
} as const;

export const easings = {
  standard: [0.4, 0, 0.2, 1],
  emphasized: [0.2, 0, 0, 1],
} as const;

export const springs = {
  snappy: { type: "spring" as const, stiffness: 400, damping: 30 },
  gentle: { type: "spring" as const, stiffness: 120, damping: 20 },
} as const;

export const fadeInUp = {
  initial: { opacity: 0, y: 8 },
  animate: { opacity: 1, y: 0 },
  exit: { opacity: 0, y: -4 },
  transition: { duration: durations.normal, ease: easings.emphasized },
};

export const staggerContainer = {
  animate: { transition: { staggerChildren: 0.05 } },
};

Usage in components:

import { motion } from "framer-motion";
import { fadeInUp, staggerContainer, springs } from "@/lib/motion";

export function CardGrid({ items }) {
  return (
    <motion.ul variants={staggerContainer} initial="initial" animate="animate">
      {items.map((item) => (
        <motion.li key={item.id} variants={fadeInUp}>
          <motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}
            transition={springs.snappy}>
            {item.label}
          </motion.button>
        </motion.li>
      ))}
    </motion.ul>
  );
}

Reduced motion in Framer Motion: use useReducedMotion() hook — when true, set transition: { duration: 0 } or skip initial offsets. Document in accessible UI prompts.

Layout animations vs transform animations

Smart Animate often morphs width/height. In production, transform: scale performs better than animating width. Framer Motion’s layout prop handles layout shifts but costs more — use for reordering lists, not every hover. When Figma Make to production exports animations, audit which properties trigger layout (width, height, top, left) and refactor to transform where possible.

Prompts for AI builders implementing motion

1. Create lib/motion.ts with duration, easing, and spring tokens matching our Figma motion table [paste table]. Export fadeInUp, fadeIn, slideInRight variants. No magic numbers in components.

2. Refactor components/Modal.tsx to use motion tokens: enter duration.normal easing.emphasized; exit duration.fast easing.exit. Portal to body. Trap focus. Respect useReducedMotion.

3. Button hover from Figma: scale 1.02, duration fast, spring snappy. Replace all transition-all on buttons in components/ui/button.tsx with token-based transitions only.

4. Page route transitions in app/template.tsx using Framer Motion AnimatePresence; fade only; duration normal; disable if prefers-reduced-motion.

5. Stagger children in components/FeatureList.tsx: stagger step 50ms, fadeInUp variant, max 20 items then instant (performance cap).

6. Audit project for transition-all, arbitrary durations, and layout-thrashing width animations. List files; propose token replacements.

Add motion token rules to system prompt templates and context window prompts so agents do not reintroduce random easings on every session.

Dev Mode inspection tips

In Figma Dev Mode, copy transition specs from prototype connections. If Dev Mode shows only static frames, ask design for a linked prototype or a motion spec table — vibe coding without specs guarantees rework. Cross-link vibe coding roadmap motion week with this post.

Mapping Figma easing curves to CSS bezier

When Dev Mode shows “Ease out” without numbers, use Figma’s custom bezier editor values directly. Example: Smart Animate on a modal uses 300ms with curve (0.16, 1, 0.3, 1) — paste into CSS as cubic-bezier(0.16, 1, 0.3, 1) and name it motion.easing.modal-enter in your token file. Maintain a spreadsheet during design QA: Frame name | Property changed | Duration | Easing | Delay. Export CSV to docs/motion-spec.csv and reference in prompts so agents stop inventing curves.

Stagger timing from Figma prototypes often uses 30–80ms between list items. Encode as --motion-stagger-step: 50ms and set style={{ '--index': i }} on each row. Cap stagger at 15 items then batch — animating 100 rows staggered adds perceived lag on low-end mobile.

Cross-framework motion token sharing

If your marketing site is Astro/CSS and app is Next.js/Framer Motion, publish tokens once in packages/tokens/motion.css and import in both. JSON export for Framer Motion config generator scripts. Single source prevents marketing hover at 150ms and app button at 237ms — the subtle inconsistency users feel but cannot name.

Prompt for monorepos: “Read packages/tokens/motion.css; implement Modal in apps/web using only these variables; add framer-motion wrapper in lib/motion.ts that reads same values — no duplicate numbers.”

Performance budget for motion

Animate only transform and opacity when possible — compositor-friendly. Avoid animating box-shadow, filter, and width on large lists. Use will-change: transform sparingly on active elements only; remove after animation completes. AI loves transition-all — ban it in Cursor rules; specify exact properties.

Lighthouse flags layout shifts when animations change dimensions. Smart Animate morphing card height is a design delight and a CLS risk — reserve min-height or skeleton space in CSS before animation runs.

FAQ

Should motion tokens live in Figma Variables?

Yes when your team uses Variables for design-dev parity. Export names matching code (motion.duration.normal). String variables for cubic-bezier; number variables for ms durations.

CSS transitions or Framer Motion for a Next.js SaaS?

CSS for hovers, focus, simple fades. Framer Motion for exit animations, stagger, gestures, layout reorder. Do not load Framer Motion for a static marketing site with two hovers.

How do I match Smart Animate spring exactly?

You cannot in pure CSS. Tune Framer Motion stiffness/damping until screen recording aligns within one frame at 60fps. Export comparison GIF for design sign-off.

Do AI tools respect motion tokens?

Only if you encode them in CLAUDE.md, Cursor rules, or system prompts. Otherwise agents default to transition-all duration-200. Run audit prompt 6 after every UI generation pass.

Motion tokens explained give designers and developers — and AI agents — a shared language from Figma Smart Animate to CSS and Framer Motion. Define duration and easing scales, implement once in tokens, respect reduced motion, and prompt agents to use names not numbers. Continue with Smart Animate micro-interactions and motion handoff tools to close the loop from prototype to production.

Quick reference: token naming cheat sheet

Keep motion tokens boring and predictable. A small shared vocabulary beats clever one-offs that AI reinvents every session.

  • --motion-duration-instant: 80–120ms for toggles and checkbox feedback
  • --motion-duration-fast: 150–200ms for hover and micro UI
  • --motion-duration-base: 250–300ms for most transitions
  • --motion-duration-slow: 400–500ms for page-level or modal entrances
  • --motion-ease-standard: a calm cubic-bezier for general UI
  • --motion-ease-emphasized: a slightly punchier curve for primary CTAs
  • --motion-ease-exit: a curve that feels slightly faster on the way out

When you prompt Cursor or Claude, paste this cheat sheet into your rules file and say: “Use only these tokens. Do not invent new duration values.” That single instruction prevents half of the random 0.37s ease-in-out noise that shows up after AI UI generation. Pair it with the handoff habits in our Figma motion handoff guide and you get motion that survives production, not just a pretty prototype.

Leave a Reply

Your email address will not be published. Required fields are marked *