57 motion presets for AI coding — Framer Motion, GSAP, and CSS.
Most AI coding tools have one motion default: fade-in-up, 600ms, on everything. That's the slop. ux-skill's motion manifest ships 57 named presets, each available as Framer Motion config, GSAP call, and raw CSS — with explicit prefers-reduced-motion branches and performance notes. Same intent, three engines, queryable as data. Here's the tour.
The shape of one entry
Pulled from data/motion-presets.json. Every preset has an id, a name, a category, an engine-agnostic tokens block (duration, easing, transform), and three engine-specific snippets ready to paste. Plus a reduced-motion branch and a performance note.
// data/motion-presets.json — entries[0] { "id": "fade-up-12px", "name": "Fade-Up 12px", "category": "Entry", "tokens": { "duration_ms": 360, "easing": "cubic-bezier(0.16, 1, 0.3, 1)", "transform_from": "translateY(12px)", "transform_to": "translateY(0)", "opacity_from": 0, "opacity_to": 1 }, "stacks": { "framer_motion": "{ initial: { y: 12, opacity: 0 }, animate: { y: 0, opacity: 1 }, transition: { duration: 0.36, ease: [0.16, 1, 0.3, 1] } }", "gsap": "gsap.from(el, { y: 12, opacity: 0, duration: 0.36, ease: 'expo.out' })", "css": "@keyframes fade-up-12 { from { transform: translateY(12px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .fade-up-12 { animation: fade-up-12 360ms cubic-bezier(0.16, 1, 0.3, 1) both; }" }, "reduced_motion": "instant — fade only, no transform", "performance": "transform + opacity only — GPU composited" }
The tokens block is engine-agnostic and durable: when someone wants to add a fourth engine (Motion One, Anime.js, JS Web Animations API) the work is generating new snippets from those same tokens — not redoing the intent.
The 57 presets at a glance
Eight categories, 57 entries. The breakdown matches the way a real product surface needs motion — not just entry animations.
| Category | Count | Representative presets |
|---|---|---|
| Entry | 10 | fade-up-12px, fade-in-pure, scale-in-soft, slide-up-card, blur-in-12 |
| Hover | 8 | lift-2px, tint-shift, border-warm, icon-rotate-12, card-tilt-2deg |
| Transition | 8 | route-crossfade, tab-slide, drawer-push, view-trans-card |
| Scroll | 7 | scroll-pin-hold, parallax-depth-3, reveal-stagger, scrub-fold |
| State | 7 | state-flip, loading-skeleton, success-tick, error-shake |
| Exit | 6 | fade-out-fast, shrink-out, slide-down-exit |
| Attention | 6 | pulse-soft, glow-cycle, row-highlight, shake-error |
| Press | 5 | press-scale-97, press-darken, haptic-bounce |
| Total | 57 | Every preset in every category has all three engine snippets. |
One preset, three engines — fade-up-12px
Here's the most-used Entry preset in all three forms, with the reduced-motion branch:
Fade-Up 12px
fade-up-12px · EntryFramer Motion
import { motion } from 'framer-motion'; <motion.div initial={{ y: 12, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.36, ease: [0.16, 1, 0.3, 1] }} > <Card /> </motion.div>
GSAP
import { gsap } from 'gsap'; gsap.from(el, { y: 12, opacity: 0, duration: 0.36, ease: 'expo.out' });
CSS
@keyframes fade-up-12 { from { transform: translateY(12px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .fade-up-12 { animation: fade-up-12 360ms cubic-bezier(0.16, 1, 0.3, 1) both; } /* Reduced motion — fade only, no transform */ @media (prefers-reduced-motion: reduce) { .fade-up-12 { animation: fade-in-200ms linear both; } }
prefers-reduced-motion is not optional
Every preset declares a reduced_motion contract. Three categories of behavior, depending on what the motion is doing:
- "no change" — the preset is already opacity-only or covers a state transition that the user expects. Keep as is.
- "instant — fade only, no transform" — keep the opacity arc, drop the transform. The semantics survive; the velocity doesn't.
- "omit — render final state immediately" — for decorative attention-grabbers like
pulse-softandglow-cycle, the motion is dropped entirely.
The linter has a rule (fade-in-up-no-reduced-motion) that fires on any animation using translate without a @media (prefers-reduced-motion: reduce) branch nearby. The linter's 51 rules includes four motion-category rules; this is the one that catches the AI default most often.
When to pick each engine
Pick CSS when
The motion is declarative, doesn't need to be controlled by state, and the duration is under 1.2 seconds. CSS keyframes are the cheapest to ship (no JS, no library, no runtime cost) and the most performant — the browser runs them on the compositor thread. Use CSS for Entry animations, Hover lifts, Press states, basic Attention, and most Exit animations.
Bytes shipped: zero. Composite cost: minimal. Control surface: minimal.
Pick Framer Motion when
The animation is bound to React state — a route change, a tab switch, a drawer open, a layout shift. Framer Motion's AnimatePresence and layoutId patterns are unmatched for component-tree animations. Also pick it when you need gestures (drag, hover with imperative control) or layout animations (FLIP).
Bytes shipped: ~35KB gzipped (tree-shakeable). Composite cost: low. Control surface: deep React integration.
Pick GSAP when
The animation is a timeline — multiple elements with overlapping cues, scroll-driven scrubbing, complex easing, or staggers. GSAP's ScrollTrigger is the canonical tool for scroll-pinned cinema (the scroll-pin-hold preset). It's also the right pick when the motion needs to run outside React (vanilla JS, Svelte, Astro islands).
Bytes shipped: ~40KB gzipped core, +15KB ScrollTrigger. Composite cost: moderate (runs on rAF). Control surface: timeline-first.
A rule of thumb that holds up
For most product surfaces, the right split is: CSS for 70% of the motion (entry, hover, press, simple transitions), Framer Motion for 20% (route, drawer, layout), GSAP for 10% (scroll cinema, complex timelines). If your codebase is 90% Framer Motion, you're probably paying React-tree-traversal costs for animations CSS would do for free.
Performance — only animate cheap properties
Every preset's performance field documents which properties get animated. The shortlist of cheap ones — properties the browser can composite without layout or paint:
transform— translate, scale, rotate, skew. Free.opacity— free.filter— moderate cost;bluris heavy.
The shortlist of expensive ones — anything that triggers layout:
top,left,width,height,margin,padding— all expensive. The linter (animate-layout-property) catches these.box-shadow— moderate; considerfilter: drop-shadowon the parent.
Every preset in the manifest is built from cheap properties. If you need an expensive animation, you build it yourself and document why.
Querying motion from your agent
The presets are exposed over MCP via the ux_motion_presets tool. The agent can pull a single preset, the full catalog, or a filtered subset:
// Filter by category, then take the first match const presets = await mcp.call("ux_motion_presets", { category: "Scroll" }); // Returns: scroll-pin-hold, parallax-depth-3, reveal-stagger, scrub-fold, ... // Or pull one by id const preset = await mcp.call("ux_motion_presets", { id: "scroll-pin-hold" });
Full MCP server walkthrough covers the wiring for Claude Desktop, Cursor, and Windsurf.
The scroll-pin-hold story
Two of the 57 presets do real cinema work — and they're worth a dedicated section because the AI default is to scroll the page like a Word document and the cinema language is what actually feels modern in 2026.
scroll-pin-hold pins the section to the viewport for the duration of its scroll budget, runs an inner timeline scrubbed to scroll position, and releases on exit. parallax-depth-3 defines three depth layers (background 0.4x, mid 1x, foreground 1.6x of scroll velocity) so the section reads as a 3D camera move.
Both are GSAP + ScrollTrigger presets — CSS scroll-driven animations are landing in browsers but the spec is still moving, and Framer Motion's useScroll doesn't pin natively. Until the platform stabilizes, GSAP is the cleanest path.
The new dark editorial cinema homepage uses both presets on every major section.
AI coding tools ship one motion default: fade-in-up on everything. The fix is not "less motion." It's the right motion per role.
57 presets is not "every animation ever."
For bespoke product moments — a custom data viz, a branded loader, a one-off marketing micro-interaction — you'll still write motion yourself. The manifest covers the 90% case where the motion is recognizable across the industry, and the goal is to use the right one instead of inventing a worse one. The remaining 10% is where the brand earns its identity, and that's the right place to spend effort.
The Framer Motion snippets target v11+. The GSAP snippets target v3+. The CSS snippets are evergreen.
How motion plugs into the rest of the engine
The recommender doesn't ship motion in isolation. When you run /ux-recommend against a brief, the response includes a curated motion subset — typically 4 to 7 presets — picked to match the style and tone. A "calm editorial" recommendation pulls fade-up-12px, scroll-pin-hold, and lift-2px. An "operational density" recommendation pulls state-flip, row-highlight, and success-tick. The presets are the same; the curation is the design call.
More on how the recommender composes a system, including which manifests it pulls from.
Related reading
- The 51 AI design fingerprints — including the motion fingerprints.
- Best Claude Code design skills — which ones ship motion libraries.
- Dark editorial cinema — scroll-pin-hold in production.
- Regex linter — including the four motion rules.
- MCP — calling motion presets from any host.
- Compare — ui-ux-pro-max doesn't ship a motion library.