A motion spec is a written, shared description of an animation — trigger, duration, easing, delay, animated properties, reduced-motion behavior, and performance budget — produced before development starts, so designers and developers implement the same motion instead of negotiating it from memory. Verbal motion briefs fail because animation has too many degrees of freedom: “fade the section in as you scroll” leaves start/end opacity, distance, easing curve, duration, trigger point, and reduced-motion fallback entirely unspecified, and each gap becomes a different guess in code. A usable motion spec fits on one screen per moment (or one table row), uses units and named easings rather than adjectives, and is referenced in the ticket’s acceptance criteria. Teams that document motion this way ship animations that match the design on the first review pass, avoid accessibility regressions, and keep performance budgets intact — the same discipline that makes white-label Figma to WordPress processes predictable.
This guide covers why informal motion briefs break, the exact fields every motion spec needs, Figma annotations versus dedicated spec docs, a handoff meeting agenda, a fully worked example of one scroll-reveal spec, and a copy-paste template table you can drop into your process. For how motion work fits into a time-boxed engagement, see motion sprint explained.
Why verbal motion briefs fail
Motion is ephemeral by nature — it exists only at runtime, so a conversation leaves no artifact QA can check against. Typical failure modes:
- Adjective drift. “Smooth” means
cubic-bezier(0.22, 1, 0.36, 1)to one person and a linear 600ms fade to another. - Trigger ambiguity. Does the reveal fire at 80% viewport, when the element enters the bottom 100px, or on image load?
- Property ambiguity. Fade only? Fade + rise 24px? Scale from 0.96? Each choice changes CLS risk and perceived style.
- Reduced-motion silence. Nobody decides what happens when the user asks for less motion — so developers either ship nothing or ship too much.
- No performance ceiling. The “just one more tween” pattern quietly breaks the page’s motion budget.
Written specs convert taste into testable statements. If the spec says 500ms, ease-out, opacity + translateY(24px), trigger at 75% viewport, reduced-motion = instant visible — QA can verify every clause without opening a Figma prototype.
The fields of a motion spec
Every animated moment needs these fields. Missing any one of them invites a guess:
| Field | Example | Why it matters |
|---|---|---|
| Name / ID | reveal--features-grid | Stable reference for design, code, QA, analytics |
| Trigger | IntersectionObserver, 75% viewport, once | When it starts; scroll vs hover vs load vs route |
| Duration | 500ms (±50ms tolerance) | Perceived pacing; total main-thread exposure |
| Easing | Named token, e.g. ease-out-quart → cubic-bezier(0.25, 1, 0.5, 1) | Personality; avoids “make it feel premium” loops |
| Delay | 0ms (or 80ms stagger × index) | Choreography; prevents timeline pile-ups |
| Properties | opacity: 0→1, transform: translateY(24px)→0 | CLS safety; only transform/opacity for in-flow reveals |
| Initial state (pre-JS) | Visible by default; hidden only after JS marks html.js-motion | No-JS and SEO: content never trapped invisible |
| Reduced-motion | Set final state immediately; duration 0; no transform | Accessibility; often a merge gate |
| Performance budget | Transform/opacity only; no layout props; ≤1 ScrollTrigger group | Keeps CLS <0.1 and INP healthy |
| Interrupt / reverse behavior | Once: true (no reverse on scroll up) | Prevents flicker and extra work |
| Owner / status | Design: Ana · Dev: Luis · Status: Approved | Single source of truth for questions |
Figma annotations vs a dedicated motion spec
Both work; they solve different problems.
Figma annotations
Good for: pinning duration/easing callouts next to frames, recording prototype interactions, keeping visual context attached to layout. Use sticky annotations or a dedicated annotation layer with the same field names as your spec table. Limitations: prototypes simulate motion, they don’t encode exact tokens, and comments get resolved/lost. Figma is also the wrong place for reduced-motion and performance clauses.
Dedicated spec (table in Notion/Docs/ticket, or code-adjacent config)
Good for: full field coverage, versioning, QA checklists, and review in PR templates. A single “Motion Spec” table per page (one row per moment) scales better than dozens of sticky notes. The strongest setups keep the table as the contract and, during implementation, mirror names into a central config (e.g., a GSAP registry) so the spec stays greppable — patterns like those in GSAP motion systems in production make the spec and the code use identical moment IDs.
Recommended hybrid: annotate the Figma frame with the moment ID and headline values; keep the full row (trigger, reduced-motion, budget, QA notes) in the shared spec table linked from the ticket.
Handoff meeting agenda (30 minutes)
- 5 min — Page motion budget recap. How many moments this template allows; what was cut.
- 10 min — Moment-by-moment walkthrough. For each spec row: play prototype, confirm trigger/properties/reduced-motion; resolve adjectives into tokens.
- 5 min — Initial states & no-JS. Confirm content is visible without JS; decide the class/flag strategy for hiding pre-animation.
- 5 min — Reduced-motion & a11y. Agree the fallback per moment (usually: jump to end state). Reference your reduced-motion guide if the team needs shared rules.
- 5 min — Open questions, owners, ship criteria. Who approves deviations; what QA checks; link spec from acceptance criteria.
Rule: the meeting ends only when every adjective has a number and every moment has a reduced-motion clause.
Worked example: scroll reveal on a features grid
Moment ID: reveal--features-grid
- Trigger: element enters viewport at 75% (IntersectionObserver or ScrollTrigger
start: "top 75%"), fires once, no reverse. - Duration: 500ms.
- Easing: token
ease-out-quart=cubic-bezier(0.25, 1, 0.5, 1). - Delay / stagger: 80ms per card, max 4 cards staggered (320ms tail); remaining cards reveal together.
- Properties: cards
opacity 0→1,transform translateY(24px)→0. No width/height/margin animation. - Initial state: cards fully visible in HTML/CSS. JS adds
data-motion="pending"only when animation will run and reduced-motion is not set; CSS hides only pending nodes. - Reduced-motion: remove pending state; final values applied immediately; duration 0.
- Performance budget: transform/opacity only; single observer; passive scroll; no per-frame layout reads.
- Acceptance: matches prototype within ±50ms duration; Lighthouse CLS unchanged; axe no violations; with
prefers-reduced-motion: reduce, cards appear instantly.
That paragraph is enough for a developer to implement without Slack archaeology — and enough for QA to test without watching a prototype ten times.
Copy-paste motion spec template
| Moment ID | Trigger | Duration | Easing | Delay / stagger | Properties (from → to) | Pre-JS state | Reduced-motion | Perf notes | QA |
|---|---|---|---|---|---|---|---|---|---|
hero--headline | DOMContentLoaded, once | 600ms | ease-out-expo | 0 / 60ms lines | opacity 0→1; y 16→0 | Visible | Instant final state | Must not delay LCP paint | LCP <2.5s |
reveal--features-grid | top 75% viewport, once | 500ms | ease-out-quart | 0 / 80ms × 4 | opacity 0→1; y 24→0 | Visible (hide via JS flag) | Duration 0, end state | Transform/opacity only | CLS <0.1 |
nav--mega-open | hover / focus / click | 200ms | ease-out | 0 | opacity 0→1; y −8→0 | Visible if no JS? → hidden via CSS :focus-within fallback | Instant toggle | No layout animation | INP <200ms on open |
route--page-enter | view transition / route change | 250ms | ease-in-out | 0 | opacity only | n/a | Disable transition | Never block first paint | See INP/CWV notes |
Keeping the spec alive after handoff
- Link the spec table from every motion-related ticket’s acceptance criteria.
- Add PR checklist lines: matches spec IDs? reduced-motion present? no layout-animating properties?
- If implementation needs a deviation (duration 420ms instead of 500ms), update the spec row in the same PR — the table stays the contract.
- Retire moments explicitly: when a moment is cut for budget, mark the row “removed” rather than deleting history, so nobody re-adds it from an old prototype.
Documentation is the cheapest performance and accessibility tool you will ever adopt: a 15-minute spec write prevents multi-day rebuild loops, keeps white-label handoffs calm (as in structured Figma handoff checklists for developers), and makes motion reviewable instead of vibes-based.
Frequently asked questions
What is the minimum viable motion spec?
Moment ID, trigger, duration, easing, animated properties, initial/pre-JS state, reduced-motion behavior, and one performance constraint (usually transform/opacity only, plus budget impact). If a field would force a developer to guess, it belongs in the spec.
Where should the motion spec live — Figma or a document?
Use both lightly: annotate Figma with the moment ID and key values for context; keep the full field table in a shared doc or ticket that QA and engineering reference. Figma prototypes cannot reliably encode reduced-motion or performance rules.
How do I handle reduced-motion in the spec?
Give every moment an explicit clause — almost always “apply final state immediately, duration 0, no transform/opacity interpolation.” Never leave it implicit; silence defaults to whatever the developer implements, which is often the animated path.
Should designers specify easing curves numerically?
Yes, via named tokens mapped to cubic-bezier values or GSAP ease names (e.g., power2.out). Tokens keep brand personality consistent across pages and make “it feels different” conversations resolvable against the spec.
How does a motion spec connect to performance budgets?
Each spec row states its perf notes (allowed properties, observer strategy, whether it delays LCP). Summing rows against the page’s moment cap keeps the design inside the motion budget — the same allocation discipline described for page-level budgets in motion budget planning for marketing sites.
Read next: white-label web development for agencies · pixel-perfect Figma to WordPress development · GSAP motion and page transitions
