Framer Motion Pattern
Motion.dev (formerly Framer Motion) core patterns: AnimatePresence exit animation, layout layoutId shared-element, whileHover/whileTap micro-interactions, and useReducedMotion hook — React-declarative animation for compo…
$ prime install @community/template-framer-motion-pattern Projection
Always in _index.xml · the agent never has to ask for this.
FramerMotionPattern [template] v1.0.0
Motion.dev (formerly Framer Motion) core patterns: AnimatePresence exit animation, layout layoutId shared-element, whileHover/whileTap micro-interactions, and useReducedMotion hook — React-declarative animation for component enter/exit and layout shifts.
Loaded when retrieval picks the atom as adjacent / supporting.
FramerMotionPattern [template] v1.0.0
Motion.dev (formerly Framer Motion) core patterns: AnimatePresence exit animation, layout layoutId shared-element, whileHover/whileTap micro-interactions, and useReducedMotion hook — React-declarative animation for component enter/exit and layout shifts.
Language
typescript
Body
// npm install motion
// All imports from "motion/react" (Motion.dev v11+, formerly "framer-motion")
import { motion, AnimatePresence, useReducedMotion } from "motion/react";
import { useState } from "react";
// ── Pattern A: AnimatePresence enter/exit ─────────────────────────────
// AnimatePresence allows components to animate out before unmounting.
function NotificationList({ notifications }) {
return (
<AnimatePresence mode="popLayout">
{notifications.map((n) => (
<motion.div
key={n.id}
layout
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -10, scale: 0.95 }}
transition={{ duration: 0.25, ease: [0.16, 1, 0.3, 1] }}
>
{n.message}
</motion.div>
))}
</AnimatePresence>
);
}
// ── Pattern B: layoutId shared-element transition ─────────────────────
// The indicator slides to whichever tab is active — zero position math.
function TabBar({ tabs }) {
const [active, setActive] = useState(0);
return (
<nav style={{ display: "flex" }}>
{tabs.map((tab, i) => (
<button key={tab} onClick={() => setActive(i)}
style={{ position: "relative", padding: "8px 20px" }}>
{tab}
{active === i && (
<motion.div
layoutId="underline"
style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: 2, background: "currentColor" }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>
)}
</button>
))}
</nav>
);
}
// ── Pattern C: whileHover / whileTap micro-interaction ────────────────
function Card({ children }) {
const shouldReduce = useReducedMotion();
return (
<motion.div
whileHover={shouldReduce ? {} : { y: -4, boxShadow: "0 12px 30px oklch(0 0 0 / 0.15)" }}
whileTap={shouldReduce ? {} : { scale: 0.97 }}
transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
>
{children}
</motion.div>
);
}
// ── Pattern D: useReducedMotion hook ──────────────────────────────────
// Returns true when prefers-reduced-motion: reduce is active.
// Use to conditionally skip or simplify animations in declarative JSX.
function HeroSection() {
const shouldReduce = useReducedMotion();
return (
<motion.h1
initial={shouldReduce ? false : { opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={shouldReduce ? { duration: 0 } : { duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
>
Hero text
</motion.h1>
);
}
Usage Notes
- Import from 'motion/react' (v11+) not 'framer-motion' — package was renamed.
- AnimatePresence mode='popLayout' — when items are removed, remaining items reposition smoothly.
- layoutId animations use Framer's FLIP internally — no manual position calculation needed.
- useReducedMotion() is reactive — returns updated value if OS preference changes mid-session.
- spring stiffness:400, damping:30 = the 'snappy indicator' preset. stiffness:180, damping:18 = 'gentle default'.
- Motion.dev is React-first — for non-React or complex scroll animations, use GSAP (see rule-gsap-now-free).
Accessibility
- useReducedMotion() is the idiomatic Motion.dev reduced-motion check — use it instead of raw matchMedia.
- AnimatePresence exit animations can delay screen reader announcements if duration is long (>300ms).
- layout prop on list items must not cause focus to jump — verify with keyboard tabbing during animations.
Loaded when retrieval picks the atom as a focal / direct hit.
FramerMotionPattern [template] v1.0.0
Motion.dev (formerly Framer Motion) core patterns: AnimatePresence exit animation, layout layoutId shared-element, whileHover/whileTap micro-interactions, and useReducedMotion hook — React-declarative animation for component enter/exit and layout shifts.
Language
typescript
Body
// npm install motion
// All imports from "motion/react" (Motion.dev v11+, formerly "framer-motion")
import { motion, AnimatePresence, useReducedMotion } from "motion/react";
import { useState } from "react";
// ── Pattern A: AnimatePresence enter/exit ─────────────────────────────
// AnimatePresence allows components to animate out before unmounting.
function NotificationList({ notifications }) {
return (
<AnimatePresence mode="popLayout">
{notifications.map((n) => (
<motion.div
key={n.id}
layout
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -10, scale: 0.95 }}
transition={{ duration: 0.25, ease: [0.16, 1, 0.3, 1] }}
>
{n.message}
</motion.div>
))}
</AnimatePresence>
);
}
// ── Pattern B: layoutId shared-element transition ─────────────────────
// The indicator slides to whichever tab is active — zero position math.
function TabBar({ tabs }) {
const [active, setActive] = useState(0);
return (
<nav style={{ display: "flex" }}>
{tabs.map((tab, i) => (
<button key={tab} onClick={() => setActive(i)}
style={{ position: "relative", padding: "8px 20px" }}>
{tab}
{active === i && (
<motion.div
layoutId="underline"
style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: 2, background: "currentColor" }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>
)}
</button>
))}
</nav>
);
}
// ── Pattern C: whileHover / whileTap micro-interaction ────────────────
function Card({ children }) {
const shouldReduce = useReducedMotion();
return (
<motion.div
whileHover={shouldReduce ? {} : { y: -4, boxShadow: "0 12px 30px oklch(0 0 0 / 0.15)" }}
whileTap={shouldReduce ? {} : { scale: 0.97 }}
transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
>
{children}
</motion.div>
);
}
// ── Pattern D: useReducedMotion hook ──────────────────────────────────
// Returns true when prefers-reduced-motion: reduce is active.
// Use to conditionally skip or simplify animations in declarative JSX.
function HeroSection() {
const shouldReduce = useReducedMotion();
return (
<motion.h1
initial={shouldReduce ? false : { opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={shouldReduce ? { duration: 0 } : { duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
>
Hero text
</motion.h1>
);
}
Usage Notes
- Import from 'motion/react' (v11+) not 'framer-motion' — package was renamed.
- AnimatePresence mode='popLayout' — when items are removed, remaining items reposition smoothly.
- layoutId animations use Framer's FLIP internally — no manual position calculation needed.
- useReducedMotion() is reactive — returns updated value if OS preference changes mid-session.
- spring stiffness:400, damping:30 = the 'snappy indicator' preset. stiffness:180, damping:18 = 'gentle default'.
- Motion.dev is React-first — for non-React or complex scroll animations, use GSAP (see rule-gsap-now-free).
Accessibility
- useReducedMotion() is the idiomatic Motion.dev reduced-motion check — use it instead of raw matchMedia.
- AnimatePresence exit animations can delay screen reader announcements if duration is long (>300ms).
- layout prop on list items must not cause focus to jump — verify with keyboard tabbing during animations.
Source
prime-system/examples/frontend-design/primes/compiled/@community/template-framer-motion-pattern/atom.yaml