ux
Blog · Reference · 2026-05-28

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
Entry10fade-up-12px, fade-in-pure, scale-in-soft, slide-up-card, blur-in-12
Hover8lift-2px, tint-shift, border-warm, icon-rotate-12, card-tilt-2deg
Transition8route-crossfade, tab-slide, drawer-push, view-trans-card
Scroll7scroll-pin-hold, parallax-depth-3, reveal-stagger, scrub-fold
State7state-flip, loading-skeleton, success-tick, error-shake
Exit6fade-out-fast, shrink-out, slide-down-exit
Attention6pulse-soft, glow-cycle, row-highlight, shake-error
Press5press-scale-97, press-darken, haptic-bounce
Total57Every 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 · Entry
360ms expo.out easing transform + opacity

Framer 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:

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:

The shortlist of expensive ones — anything that triggers layout:

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.
Honesty card

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

Install the engine

One library. Three engines. 57 presets.

Same manifest powers the CLI, the MCP server, and the recommender — every preset available as Framer Motion, GSAP, and CSS. Reduced-motion handling included. MIT, no telemetry, no account.

$ /plugin marketplace add Laith0003/ux-skill
$ /plugin install ux@ux-skill
— or —
$ pip install uxskill
— or —
$ npx uxskill@alpha init