Animation principles
Skill mshadmanrahman/pm-operating-system/_context/skills/animation-principles
An opinionated workflow system that restructures how product managers work with Claude Code. 16 PM skills, 9 agents, 10 coding rules, 13 slash commands, 6 automation hooks, product brain system, and session lifecycle with memory/handoffs.
npx -y skills add mshadmanrahman/pm-operating-system --skill animation-principlesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Disney's 12 animation principles applied to web and mobile UI. Use when implementing CSS animations, JS transitions, micro-interactions, page transitions, loading states, hover effects, or motion design. Covers timing, easing, accessibility (prefers-reduced-motion), and framework patterns (Framer Motion, CSS native, GSAP). Triggers: animate, transition, motion, hover effect, loading animation, page transition, micro-interaction, easing, spring physics.
SKILL.md
12.3 KB, as published. Nobody here has run it
Animation Principles for UI
Disney's 12 animation principles adapted for web and mobile interfaces. Based on dylantarre/animation-principles.
The 12 Principles: Web Implementation
| # | Principle | Web Implementation | When to Use |
|---|---|---|---|
| 1 | Squash & Stretch | transform: scale() on interaction states | Button press, toggle bounce, badge update |
| 2 | Anticipation | Slight reverse movement before action | Dropdown opens, drag start, navigation |
| 3 | Staging | Focus attention via motion hierarchy | Modal focus, form field activation |
| 4 | Straight Ahead / Pose to Pose | JS frame-by-frame vs CSS keyframes | Progress (straight) vs state snaps (pose) |
| 5 | Follow Through / Overlapping | Staggered child animations, elastic easing | Menu items, list entries, content settle |
| 6 | Slow In / Slow Out | ease-in-out, cubic-bezier curves | Every UI transition (never use linear) |
| 7 | Arc | motion-path or bezier translate transforms | Toggle switches, circular menus |
| 8 | Secondary Action | Shadows, glows responding to primary motion | Button shadow on hover, icon color shift |
| 9 | Timing | Duration ranges per interaction type | See timing table below |
| 10 | Exaggeration | Scale beyond 1.0, overshoot animations | Emphasis moments, error shakes |
| 11 | Solid Drawing | Consistent transform-origin, 3D perspective | All transforms need correct origin |
| 12 | Appeal | Smooth 60fps, purposeful motion | Every animation must have a reason |
Principle Details
Squash & Stretch: Apply scaleY compression on button press, scaleX stretch on hover. Keep volume constant: if you compress Y, expand X slightly. Keep subtle in UI; this is interfaces, not cartoons.
Anticipation: Before expanding a dropdown, shrink it 2-3% first. Before sliding content left, move it 5px right. Hover states prepare for click. Draggable items elevate on grab start.
Staging: Dim background elements during modal focus. Use motion to direct eye flow: animate important elements first. Active form fields are clearly distinguished. One interaction feedback at a time.
Straight Ahead vs Pose to Pose: Use CSS @keyframes for predictable, repeatable animations (pose to pose). Use JavaScript/GSAP for dynamic, physics-based motion (straight ahead). Progress indicators = straight ahead. Checkboxes = pose to pose.
Follow Through & Overlapping: Child elements complete movement after parent stops. Use animation-delay with decreasing values for natural stagger (30-50ms per item). Ripple effects expand past tap point. Toggle switches overshoot then settle.
Slow In / Slow Out: Never use linear for UI motion. Standard: cubic-bezier(0.4, 0, 0.2, 1). Enter: cubic-bezier(0, 0, 0.2, 1) (ease-out). Exit: cubic-bezier(0.4, 0, 1, 1) (ease-in). Exit animations shorter than enter (~60-70% of enter duration).
Arc: Elements in nature move in arcs, not straight lines. Use offset-path or combine X/Y transforms with different easings. Toggle switches travel in slight arc. Circular action buttons expand radially.
Secondary Action: Button shadow grows/blurs on hover. Icon inside button rotates while button scales. Background particles respond to primary element. Shadow responds to elevation changes.
Timing: See the complete timing reference table below. Micro-interactions: 100-200ms. Standard transitions: 200-400ms. Complex sequences: 400-700ms.
Exaggeration: Hover states scale to 1.05-1.1, not 1.01. Error shakes move 10-20px, not 2px. Make motion noticeable but not jarring. Save theatrics for key moments (onboarding, achievement).
Solid Drawing: Maintain consistent transform-origin. Use perspective for 3D depth. Avoid conflicting transforms. Buttons scale from center, tooltips from pointer.
Appeal: Target 60fps using transform and opacity only. Add personality through custom easing curves. Motion should feel intentional: if you can't explain why something animates, remove it.
Timing Reference
By Interaction Type
| Interaction | Duration | Easing |
|---|---|---|
| Hover | 100ms | ease-out |
| Click/tap feedback | 100ms | ease-out |
| Toggle switch | 150-200ms | spring/elastic |
| Checkbox | 150ms | ease-out |
| Focus ring | 100ms | ease-out |
| Tooltip show | 150ms | ease-out |
| Tooltip hide | 100ms | ease-in |
| Badge update | 200ms | elastic |
| Form error | 200ms | ease-out |
| Dropdown open | 200-300ms | ease-out |
| Modal open | 250-350ms | ease-out |
| Modal close | 200-300ms | ease-in |
| Page slide forward | 300-400ms | ease-out |
| Page slide back | 250-350ms | ease-out |
| Tab switch | 150-200ms | ease-out |
| Shared element | 300-400ms | ease-in-out |
By Time Scale
| Scale | Duration | Use For | Easing |
|---|---|---|---|
| Instant | 0-100ms | Hover, focus, tap feedback, ripples | ease-out or step |
| Micro | 100-200ms | Toggles, checkboxes, tooltips, badges | ease-out, cubic-bezier(0.2, 0, 0, 1) |
| Small | 200-300ms | Dropdowns, accordions, slides, fades | ease-out, spring |
| Medium | 300-500ms | Page transitions, modals, drawers | ease-in-out, cubic-bezier(0.4, 0, 0.2, 1) |
| Large | 500-800ms | Complex sequences, onboarding reveals | cubic-bezier(0.16, 1, 0.3, 1) |
Code Patterns
Micro-interactions
/* Button with squash/stretch */
.button {
transition: transform 100ms ease-out, box-shadow 100ms ease-out;
}
.button:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.button:active {
transform: translateY(0) scale(0.98);
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
/* Toggle switch with overshoot */
.toggle-thumb {
transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.toggle-thumb.active {
transform: translateX(20px);
}
/* Checkbox draw effect */
.checkmark {
stroke-dasharray: 20;
stroke-dashoffset: 20;
transition: stroke-dashoffset 200ms ease-out 50ms;
}
.checkbox:checked + .checkmark {
stroke-dashoffset: 0;
}
/* Dropdown with anticipation */
.dropdown-enter {
animation: dropdown-open 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes dropdown-open {
0% { transform: scaleY(0.98); opacity: 0; }
100% { transform: scaleY(1); opacity: 1; }
}
Page Transitions
/* Crossfade (default) */
.page-exit-active {
opacity: 0;
transition: opacity 200ms ease-in;
}
.page-enter {
opacity: 0;
}
.page-enter-active {
opacity: 1;
transition: opacity 200ms ease-out;
}
/* Slide (hierarchical navigation) */
.page-enter {
transform: translateX(100%);
}
.page-enter-active {
transform: translateX(0);
transition: transform 300ms ease-out;
}
.page-exit-active {
transform: translateX(-30%);
transition: transform 300ms ease-in;
}
/* Shared element (View Transitions API) */
.hero-image {
view-transition-name: hero;
}
::view-transition-old(hero),
::view-transition-new(hero) {
animation-duration: 300ms;
}
Stagger Pattern
/* List items entering with stagger */
.list-item {
opacity: 0;
transform: translateY(10px);
animation: stagger-in 300ms ease-out forwards;
}
.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 40ms; }
.list-item:nth-child(3) { animation-delay: 80ms; }
.list-item:nth-child(4) { animation-delay: 120ms; }
@keyframes stagger-in {
to { opacity: 1; transform: translateY(0); }
}
Framer Motion (React)
/* Spring-based animation */
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ type: "spring", stiffness: 300, damping: 24 }}
/>
/* Stagger children */
<motion.ul variants={{
show: { transition: { staggerChildren: 0.05 } }
}}>
<motion.li variants={{
hidden: { opacity: 0, y: 10 },
show: { opacity: 1, y: 0 }
}} />
</motion.ul>
/* Layout animation (shared element) */
<motion.div layoutId="hero-card" />
/* Reduced motion */
const prefersReducedMotion = useReducedMotion();
<motion.div
animate={{ x: 100 }}
transition={prefersReducedMotion ? { duration: 0 } : { type: "spring" }}
/>
Easing Reference
| Name | CSS Value | Use |
|---|---|---|
| Standard | cubic-bezier(0.4, 0, 0.2, 1) | General purpose |
| Enter (decelerate) | cubic-bezier(0, 0, 0.2, 1) | Elements appearing |
| Exit (accelerate) | cubic-bezier(0.4, 0, 1, 1) | Elements leaving |
| Sharp | cubic-bezier(0.4, 0, 0.6, 1) | Quick, snappy |
| Overshoot | cubic-bezier(0.34, 1.56, 0.64, 1) | Playful, bouncy |
| Smooth out | cubic-bezier(0.16, 1, 0.3, 1) | Elegant settle |
| Spring (CSS approx) | cubic-bezier(0.175, 0.885, 0.32, 1.275) | Natural feel |
Navigation Direction Model
| Pattern | Transition | Direction |
|---|---|---|
| Drill-down (list to detail) | Slide left / shared element | Right = forward |
| Tab bar | Fade / horizontal slide | Follows tab position |
| Bottom sheet | Slide up from bottom | Vertical |
| Modal | Scale + fade from trigger | Z-axis |
| Back button | Reverse of forward | Left = back |
| Forward navigation | Animate left/up | Deeper into hierarchy |
| Backward navigation | Animate right/down | Toward home |
Accessibility: Reduced Motion
CSS Implementation
/* Default: full animation */
.element {
transition: transform 300ms ease-out;
}
/* Reduced motion: replace with opacity or instant */
@media (prefers-reduced-motion: reduce) {
.element {
transition: opacity 200ms ease-out;
/* Or: transition: none; */
}
/* Kill all animations globally */
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Safe vs Harmful Motion
| Safe | Potentially Harmful | Always Avoid |
|---|---|---|
| Opacity fades | Parallax scrolling | Auto-playing video backgrounds |
| Color transitions | Background movement | Infinite looping animations |
| Small scale (<5%) | Zoom animations | Flashing (seizure risk) |
| Very slow movement (500ms+) | Spinning/rotating | Rapid zoom in/out |
| Non-repeating animations | Fast repeated animations | >3 flashes per second |
| Large moving areas (>1/4 viewport) | Vestibular-triggering patterns |
WCAG Compliance
- 2.3.3: Motion from interactions can be disabled unless essential
- 2.2.2: Moving content >5 seconds must be pausable
- 2.3.1: No content flashes >3 times per second
Performance Rules
- Animate only
transformandopacityfor GPU acceleration - Use
will-changesparingly; remove after animation completes - Prefer CSS over JavaScript when animation is predictable
- Keep per-frame work under ~16ms for 60fps
- Test on low-powered devices
- Debounce scroll/resize-triggered animations
- Use
requestAnimationFramefor JS animations - Virtualize animated lists with 50+ items
Pre-Delivery Checklist
-
prefers-reduced-motionrespected globally - All animations have reduced/no-motion alternative
- No auto-playing motion over 5 seconds
- Every interactive element has feedback (hover, active, focus)
- Disabled states have no animation, reduced opacity
- Exit animations are shorter than enter (~60-70%)
- No
lineareasing on UI transitions - Only
transformandopacityanimated (no width/height/top/left) - Stagger delay is 30-50ms per item (not simultaneous)
- No animation blocks user input
- Animations are interruptible (user gesture cancels in-progress motion)
- Motion direction is consistent with navigation model