원클릭으로
animating-motion
Motion design and animation patterns for UI based on Emil Kowalski's principles
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Motion design and animation patterns for UI based on Emil Kowalski's principles
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Creative direction for AI image generation — distill a codebase's FEEL canon (taste tokens, TDRs, persona files) into prompt-ready material vocabulary, map product architecture to scene systems, and enforce visual discipline across banner sets and scene stacks.
Analyze feedback logs to detect design preference patterns. Auto-contributes HIGH confidence patterns upstream.
Touch, keyboard, and form interaction patterns for accessible UI
Design physics system for UI interactions - sync strategies, timing, confirmations
Convert vague "feel" feedback into specific actionable fixes via decomposition questions
Component design patterns - compound components, composition, props API
| name | animating-motion |
| description | Motion design and animation patterns for UI based on Emil Kowalski's principles |
| user-invocable | true |
| allowed-tools | Read, Write, Glob, Grep, Edit |
Expert motion design and animation patterns for UI, based on Emil Kowalski's "Animations on the Web" principles.
/animate
This skill provides guidance on implementing performant, accessible, and delightful animations in web interfaces. Use it when:
Is this element entering or exiting?
└── YES → Use ease-out
Is an on-screen element moving?
└── YES → Use ease-in-out
Is this a hover/color transition?
└── YES → Use ease
Will users see this 100+ times daily?
└── YES → Don't animate it (or drastically reduce)
Determine what kind of animation you need:
| Scenario | Easing | Why |
|---|---|---|
| Dropdowns, modals, tooltips | ease-out | User-initiated, needs instant response |
| Element moving on screen | ease-in-out | Mimics natural acceleration/deceleration |
| Hover states, color changes | ease | Gentle, elegant transitions |
| Drag with momentum | Spring | Physics-based, interruptible |
Use for user-initiated interactions. Creates instant, responsive feeling.
/* Sorted weak to strong */
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
--ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);
Use when elements already on screen need to move or morph.
/* Sorted weak to strong */
--ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
--ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
--ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
--ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
Use for hover states and color transitions.
transition: background-color 150ms ease;
Only use for constant-speed animations (marquees, progress indicators).
Avoid - makes interfaces feel sluggish due to slow start.
| Element Type | Duration |
|---|---|
| Micro-interactions | 100-150ms |
| Standard UI (tooltips, dropdowns) | 150-250ms |
| Modals, drawers | 200-300ms |
| Page transitions | 300-400ms |
Duration Rules:
The Golden Rule: Only animate transform and opacity.
These skip layout and paint stages, running entirely on the GPU.
Avoid animating:
padding, margin, height, width (trigger layout)blur filters above 20px (expensive, especially Safari)Optimization:
/* Force GPU acceleration */
.animated-element {
will-change: transform;
}
React-specific:
Framer Motion hardware acceleration:
// Hardware accelerated (transform as string)
<motion.div animate={{ transform: "translateX(100px)" }} />
// NOT hardware accelerated (more readable but slower)
<motion.div animate={{ x: 100 }} />
Every animated element needs a prefers-reduced-motion media query:
.modal {
animation: fadeIn 200ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
.modal {
animation: none;
}
}
Reduced Motion Guidelines:
animation: none or transition: none (no !important)Framer Motion Implementation:
import { useReducedMotion } from "framer-motion";
function Component() {
const shouldReduceMotion = useReducedMotion();
return (
<motion.div
initial={shouldReduceMotion ? false : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
/>
);
}
Springs feel more natural because they simulate real physics with no fixed duration.
Apple's approach (recommended):
// Duration + bounce (easier to understand)
{ type: "spring", duration: 0.5, bounce: 0.2 }
Traditional physics:
// Mass, stiffness, damping (more complex)
{ type: "spring", mass: 1, stiffness: 100, damping: 10 }
Springs maintain velocity when interrupted—CSS animations restart from zero. This makes springs ideal for gestures users might change mid-motion.
Elements that animate together must use the same easing and duration.
/* Modal + overlay move as a unit */
.modal { transition: transform 200ms ease-out; }
.overlay { transition: opacity 200ms ease-out; }
| Usage Frequency | Animation Approach |
|---|---|
| 100+ times/day | No animation (or drastically reduced) |
| Occasional use | Standard animation |
| Rare/first-time | Can be special |
Example: Raycast never animates its menu toggle because users open it hundreds of times daily.
| Scenario | Solution |
|---|---|
| Make buttons feel responsive | Add transform: scale(0.97) on :active |
| Element appears from nowhere | Start from scale(0.95), not scale(0) |
| Shaky/jittery animations | Add will-change: transform |
| Hover causes flicker | Animate child element, not parent |
| Popover scales from wrong point | Set transform-origin to trigger location |
| Sequential tooltips feel slow | Skip delay/animation after first tooltip |
| Small buttons hard to tap | Use 44px minimum hit area (pseudo-element) |
| Something still feels off | Add subtle blur (under 20px) to mask it |
| Hover triggers on mobile | Use @media (hover: hover) and (pointer: fine) |
Important: Switching themes should not trigger transitions. Disable transitions during theme changes to prevent flash of animated content.
Use popLayout mode on AnimatePresence when an element has an exit animation and is in a group of elements.
<AnimatePresence mode="popLayout">
{items.map(item => (
<motion.div
key={item.id}
exit={{ opacity: 0, scale: 0.9 }}
/>
))}
</AnimatePresence>
When implementing drag-to-dismiss:
swipeAmount / timeTaken) > 0.10 should trigger actionPause looping animations when off-screen to save resources:
const ref = useRef(null);
const isInView = useInView(ref);
<motion.div
ref={ref}
animate={isInView ? { rotate: 360 } : {}}
transition={{ repeat: Infinity, duration: 2 }}
/>
| Aspect | CSS | JavaScript (Framer Motion) |
|---|---|---|
| Performance | Off main thread | Uses requestAnimationFrame |
| Best for | Simple, predetermined | Dynamic, interruptible |
| Interruptibility | Restarts from zero | Maintains velocity |
Apply motion that serves the interaction model: entrances use ease-out (fast start, gentle land), exits use ease-in (gentle start, fast departure), continuous movements use spring physics. Duration scales with distance traveled. Every animation has a clear purpose — guiding attention, confirming action, or showing spatial relationships.
// Target: Purpose-driven motion
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.15, ease: [0, 0, 0.2, 1] }} // ease-out for entrance
/>
What it looks like: Technically correct animations that don't serve an interaction purpose — elements that bounce, pulse, or slide without communicating state change.
// Near Miss: Motion without purpose
<motion.div
animate={{ scale: [1, 1.02, 1] }}
transition={{ repeat: Infinity, duration: 2, ease: "easeInOut" }}
/>
// This button pulses forever. Why? What state does it communicate?
Why it's tempting: Motion makes interfaces feel "alive" and "polished." Subtle animations are praised in design showcases. The animation runs smoothly and doesn't break anything.
Physics of Error: Semantic Drift — Motion without purpose trains users to ignore animation. When every element moves, movement stops signaling state change. The critical animations (loading → loaded, error → recovery, hidden → visible) lose their communicative power because they compete with decorative noise. Worse, infinite animations consume GPU compositing budget on mobile, causing the purposeful animations to stutter.
Detection signal: Any repeat: Infinity animation not attached to a loading/progress state; animations with no corresponding state transition; motion that fires on mount without user trigger.
What it looks like: Using ease-in for entrances and ease-out for exits — the physical opposite of real-world motion.
// Category Error: Inverted physics
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ ease: [0.4, 0, 1, 1] }} // ease-in for entrance — WRONG
/>
Why someone might try it: "Ease-in" sounds like "easing into view." The naming is misleading. The animation still runs and the element appears.
Physics of Error: Layer Violation — Easing curves encode physical laws. Ease-in means acceleration (slow start, fast end) — objects entering view should decelerate (fast start, slow end = ease-out). Inverting this violates the spatial metaphor that makes animation feel natural. Users perceive the interface as "off" without being able to articulate why, because the motion contradicts the physics their visual system expects. This CANNOT feel natural because it violates the perceptual model that makes animation meaningful rather than decorative.
Bridgebuilder action: Immediate rejection. Regenerate from Target with correct easing direction.
EASING SELECTION:
├── Entering/Exiting → ease-out
├── Moving on screen → ease-in-out
├── Hover/color → ease
├── Drag/gesture → spring
└── Constant speed → linear
DURATION:
├── Micro (100-150ms)
├── Standard (150-250ms)
├── Modal/Drawer (200-300ms)
└── Page (300-400ms)
PERFORMANCE:
├── Only animate: transform, opacity
├── Use: will-change: transform
└── Avoid: layout properties, heavy blur
ACCESSIBILITY:
└── Always add: @media (prefers-reduced-motion: reduce)