Loading...
Loading...
This skill should be used when the user asks to "add a hover/press effect", "animate a toggle or switch", "build an animated like button", "make a toast/snackbar slide in", "animate a drawer or modal", "animate list reordering or add/remove", "do a shared-element layout transition", or "polish UI feedback". Covers UI motion with Framer Motion (motion/react) and modern CSS.
npx skill4agent add iart-ai/web-animation-skills micro-interactiontransformopacitywidth/height/top/leftscalescale: 0.96prefers-reduced-motioncubic-bezier(0.16, 1, 0.3, 1)cubic-bezier(0.34, 1.56, 0.64, 1)cubic-bezier(0.4, 0, 0.2, 1)motion/reactframer-motionimport { motion } from "motion/react";
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.96 }}
whileFocus={{ boxShadow: "0 0 0 3px rgba(59,130,246,.5)" }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>stiffnessdampingstiffness: 400, damping: 30damping: 12AnimatePresencekeyimport { AnimatePresence, motion } from "motion/react";
<AnimatePresence>
{open && (
<motion.div
key="panel"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 8 }}
transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
/>
)}
</AnimatePresence>mode="popLayout"layout<motion.li layout transition={{ type: "spring", stiffness: 500, damping: 40 }} />layoutId{!open && <motion.div layoutId="card" onClick={() => setOpen(true)} />}
<AnimatePresence>
{open && <motion.div layoutId="card" />} {/* "magic moves" from the source */}
</AnimatePresence>layoutborder-radiuslayoutborderRadiusboxShadowlayout="position"display.toast {
transition: opacity 0.2s ease, transform 0.2s ease;
/* allow animating to/from display:none and from initial render */
transition-behavior: allow-discrete;
}
.toast[hidden] { display: none; }
/* animate FROM these values on first render / when entering */
@starting-style {
.toast { opacity: 0; transform: translateY(8px); }
}@starting-styletransition-behavior: allow-discretedisplayoverlay@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}Packaged helper ():scripts/freezes thescripts/seek-shot.sh anim.html 0 1.5 3harness and screenshots each moment;?t=Ntiles them for one-glance review. Seescripts/contact-sheet.sh sheet.png frame-*.png.scripts/README.md
motionesm.sh.html@starting-style<script type="module">motion?state=open<script>
const s = new URLSearchParams(location.search).get("state");
if (s) document.documentElement.dataset.state = s; // CSS keys off [data-state="open"]
// Framer Motion: set the controlled prop from `s` (e.g. const [open]=useState(s==="open"))
// For a CSS @keyframes loop instead, freeze it: el.style.animationDelay=(-N)+"s"; el.style.animationPlayState="paused";
window.__ready = true;
</script>?state=idle?state=hover?state=openlayoutborder-radiusnpx playwright screenshot --wait-for-timeout=400 "file://$PWD/demo.html?state=open" open.pngmotion?state=prefers-reduced-motion| Need | Approach |
|---|---|
| Press feedback | |
| Enter/exit | |
| Reorder / resize | |
| Magic move | shared |
| Toast stack | |
| No-JS enter | |
| Animate to display:none | |
| Accessibility | |
references/framer-motion-recipes.mdAnimatePresencelayoutlayoutIdReorderuseReducedMotionreferences/css-recipes.md@starting-styletransition-behavior: allow-discrete@propertyprefers-reduced-motion