- name
- design-motion-principles
- description
- Context-aware motion design skill with build and audit modes, applying three distinct philosophies from Emil Kowalski, Jakub Krehel, and Jhey Tompkins
- triggers
- ["audit the motion design in this project","add animation to this component","review these animations for quality","build an animated modal with motion design","check for AI-generated animation patterns","create an interactive component with purposeful motion","analyze motion gaps in the codebase","add enter and exit animations"]
# Design Motion Principles
> Skill by [ara.so](https://ara.so) — Design Skills collection.
A motion and interaction design skill with **two modes**: **build** interactive components with purposeful motion, or **audit** existing animations. Applies context-aware guidance from three distinct motion-design philosophies distilled from Emil Kowalski, Jakub Krehel, and Jhey Tompkins.
## Installation
```bash
npx skills add kylezantos/design-motion-principles
```
Compatible with Claude Code, Cursor, Windsurf, and other AI coding assistants.
## Core Concept
The skill applies three motion-design lenses, weighted by project context:
| Lens | Philosophy | Key Question | Best For |
|------|-----------|--------------|----------|
| **Emil Kowalski** | Restraint & speed | "Should this animate at all?" | Productivity tools, high-frequency interactions |
| **Jakub Krehel** | Production polish | "Is this subtle enough?" | Shipped consumer apps, professional refinement |
| **Jhey Tompkins** | Creative experimentation | "What could this become?" | Kids apps, portfolios, playful contexts |
The skill doesn't apply one philosophy everywhere — it weights them based on your project type, creating productive tension between approaches.
## Two Operating Modes
### 1. Create Mode
Build interactive components with motion baked in. The skill:
1. Runs discovery (project context + lens weighting)
2. Generates components (React, Framer Motion, CSS, or HTML)
3. Applies appropriate recipes, accessibility, and performance defaults
**Trigger phrases:**
- "Add animation to this modal"
- "Build an animated toast component"
- "Create a polished dropdown with motion"
### 2. Audit Mode
Review existing motion design. The skill:
1. Does reconnaissance on your project
2. Runs motion-gap analysis (finds UI that should animate but doesn't)
3. Checks against anti-AI-slop checklist
4. Proposes lens weighting
5. Delivers branded HTML report OR terminal markdown report
**Trigger phrases:**
- "Audit the motion design"
- "Review animations in this component"
- "Check for motion quality issues"
**Audit flags:**
```bash
--terminal # Output inline markdown instead of HTML report
```
Default: HTML report writes to `motion-audits/` and opens in browser.
## Mode Detection
The skill auto-detects mode from your request:
```
User: "Add enter/exit animation to this modal"
→ CREATE mode
User: "Audit the animations in this codebase"
→ AUDIT mode
User: "Review this component's motion"
→ Ambiguous — skill will ask
```
## Context-Aware Weighting
Before any work, the skill maps project type to lens weights:
**Productivity tool / Dashboard:**
```
Primary: Emil Kowalski (fast, minimal)
Secondary: Jakub Krehel (polish)
Selective: Jhey Tompkins (only for delight moments)
```
**Kids educational app:**
```
Primary: Jakub Krehel (production quality)
Secondary: Jhey Tompkins (playful)
Selective: Emil Kowalski (high-frequency interactions only)
```
**Marketing site:**
```
Primary: Jhey Tompkins (creative)
Secondary: Jakub Krehel (polish)
Selective: Emil Kowalski (utility interactions)
```
## Motion Cookbook Reference
The skill uses a single source-of-truth motion recipe library:
### Enter/Exit Patterns
**Fade + Scale (subtle):**
```css
@keyframes enter-subtle {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
.modal {
animation: enter-subtle 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
```
**Slide + Fade:**
```jsx
import { motion } from 'framer-motion';
<motion.div
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] }}
>
{content}
</motion.div>
```
**Height animation (accordion):**
```jsx
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
>
{content}
</motion.div>
```
### Easing Functions
**Emil (fast utility):**
```javascript
const ease = [0.16, 1, 0.3, 1]; // CSS: cubic-bezier(0.16, 1, 0.3, 1)
const duration = 150; // ms
```
**Jakub (subtle polish):**
```javascript
const ease = [0.25, 0.1, 0.25, 1]; // CSS: ease-in-out
const duration = 250;
```
**Jhey (playful bounce):**
```javascript
const spring = {
type: 'spring',
damping: 20,
stiffness: 300
};
```
### Framer Motion Patterns
**Conditional render with AnimatePresence:**
```jsx
import { AnimatePresence, motion } from 'framer-motion';
<AnimatePresence mode="wait">
{isOpen && (
<motion.div
key="modal"
initial={{ opacity: 0, scale: 0.96 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.96 }}
transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
>
{content}
</motion.div>
)}
</AnimatePresence>
```
**List stagger (use sparingly):**
```jsx
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.05
}
}
};
const item = {
hidden: { opacity: 0, y: 8 },
show: { opacity: 1, y: 0 }
};
<motion.ul variants={container} initial="hidden" animate="show">
{items.map(i => (
<motion.li key={i} variants={item}>
{i}
</motion.li>
))}
</motion.ul>
```
### CSS Custom Properties for Runtime Animation
```css
@property --progress {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
.progress-bar {
background: linear-gradient(
90deg,
blue var(--progress),
gray var(--progress)
);
transition: --progress 300ms ease-out;
}
```
```javascript
element.style.setProperty('--progress', '75%');
```
### Scroll-Driven Animation
```css
@supports (animation-timeline: scroll()) {
.parallax {
animation: parallax linear;
animation-timeline: scroll();
animation-range: 0 500px;
}
@keyframes parallax {
to {
transform: translateY(-100px);
}
}
}
```
## Accessibility Requirements
Every animation must include:
**1. Reduced motion support:**
```css
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
```
**Framer Motion:**
```jsx
const reducedMotion = {
initial: { opacity: 1 },
animate: { opacity: 1 },
transition: { duration: 0 }
};
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: useReducedMotion() ? 0 : 0.2
}}
/>
```
**2. Focus management:**
```javascript
// Trap focus in modal during animation
useEffect(() => {
if (isOpen) {
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
focusableElements[0]?.focus();
}
}, [isOpen]);
```
**3. Meaningful timing:**
- Duration: 150–300ms for utility, 300–500ms for expressive
- Never exceed 500ms for critical path interactions
- Match duration to distance traveled
## Anti-AI-Slop Checklist
The audit mode flags these AI-generated motion patterns:
### 1. Pulsing Indicators
```css
/* ❌ AI slop if used everywhere */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.loading { animation: pulse 2s infinite; }
```
**Heuristic:** Flag if >3 pulsing elements in viewport.
### 2. Hover-Scale-On-Everything
```css
/* ❌ AI slop if on non-pressable elements */
.card:hover {
transform: scale(1.05);
}
```
**Heuristic:** Flag if >50% of interactive elements have hover scale.
### 3. Stagger-Spam
```jsx
/* ❌ AI slop if stagger > 50ms or >10 items */
transition: { staggerChildren: 0.1 }
```
**Heuristic:** Flag if stagger used on >2 lists per page.
### 4. Blur-Everywhere Entrances
```css
/* ❌ AI slop pattern */
filter: blur(10px);
transition: filter 500ms;
```
**Heuristic:** Flag if blur used on >1 element.
### 5. Bouncy Springs on Utility
```javascript
/* ❌ AI slop for delete buttons, form submissions */
spring: { damping: 10, stiffness: 400 }
```
**Heuristic:** Flag if bounce used on destructive or utility actions.
### 6. Motion-on-Mount for Static Content
```jsx
/* ❌ AI slop: animating static header */
<motion.header initial={{ y: -100 }} animate={{ y: 0 }} />
```
**Heuristic:** Flag if page-level static elements animate.
## Performance Best Practices
**1. Animate only transform and opacity:**
```css
/* ✅ GPU-accelerated */
.fast {
transition: transform 200ms, opacity 200ms;
}
/* ❌ Causes reflow */
.slow {
transition: width 200ms, height 200ms;
}
```
**2. Use will-change sparingly:**
```css
.modal[data-state="opening"] {
will-change: transform, opacity;
}
.modal[data-state="open"] {
will-change: auto; /* Remove after animation */
}
```
**3. Avoid layout thrashing:**
```javascript
// ❌ Bad: Read then write in loop
items.forEach(item => {
const height = item.offsetHeight; // Read
item.style.height = height + 10 + 'px'; // Write
});
// ✅ Good: Batch reads, then batch writes
const heights = items.map(item => item.offsetHeight);
items.forEach((item, i) => {
item.style.height = heights[i] + 10 + 'px';
});
```
**4. Debounce resize/scroll handlers:**
```javascript
let rafId;
window.addEventListener('scroll', () => {
if (rafId) return;
rafId = requestAnimationFrame(() => {
// Animation logic
rafId = null;
});
});
```
## Common Patterns by Framework
### React + Framer Motion
**Modal:**
```jsx
import { motion, AnimatePresence } from 'framer-motion';
import { useEffect } from 'react';
export function Modal({ isOpen, onClose, children }) {
useEffect(() => {
if (!isOpen) return;
const handleEscape = (e) => {
if (e.key === 'Escape') onClose();
};
document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}, [isOpen, onClose]);
Auf GitHub ansehen