design-motion-principles
Context-aware motion design skill with build and audit modes, applying three distinct philosophies from Emil Kowalski, Jakub Krehel, and Jhey Tompkins
How do I install this agent skill?
npx skills add https://github.com/reason-machines/design-skills --skill design-motion-principlesIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill is designed for motion design tasks and includes an audit mode that analyzes existing project code. While no malicious behavior was detected, the process of reading untrusted project files creates a surface for indirect prompt injection.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
Design Motion Principles
Skill by 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
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:
- Runs discovery (project context + lens weighting)
- Generates components (React, Framer Motion, CSS, or HTML)
- 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:
- Does reconnaissance on your project
- Runs motion-gap analysis (finds UI that should animate but doesn't)
- Checks against anti-AI-slop checklist
- Proposes lens weighting
- 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:
--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):
@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:
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):
<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):
const ease = [0.16, 1, 0.3, 1]; // CSS: cubic-bezier(0.16, 1, 0.3, 1)
const duration = 150; // ms
Jakub (subtle polish):
const ease = [0.25, 0.1, 0.25, 1]; // CSS: ease-in-out
const duration = 250;
Jhey (playful bounce):
const spring = {
type: 'spring',
damping: 20,
stiffness: 300
};
Framer Motion Patterns
Conditional render with AnimatePresence:
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):
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
@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;
}
element.style.setProperty('--progress', '75%');
Scroll-Driven Animation
@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:
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Framer Motion:
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:
// 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
/* ❌ 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
/* ❌ 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
/* ❌ 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
/* ❌ AI slop pattern */
filter: blur(10px);
transition: filter 500ms;
Heuristic: Flag if blur used on >1 element.
5. Bouncy Springs on Utility
/* ❌ 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
/* ❌ 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:
/* ✅ GPU-accelerated */
.fast {
transition: transform 200ms, opacity 200ms;
}
/* ❌ Causes reflow */
.slow {
transition: width 200ms, height 200ms;
}
2. Use will-change sparingly:
.modal[data-state="opening"] {
will-change: transform, opacity;
}
.modal[data-state="open"] {
will-change: auto; /* Remove after animation */
}
3. Avoid layout thrashing:
// ❌ 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:
let rafId;
window.addEventListener('scroll', () => {
if (rafId) return;
rafId = requestAnimationFrame(() => {
// Animation logic
rafId = null;
});
});
Common Patterns by Framework
React + Framer Motion
Modal:
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]);
return (
<AnimatePresence>
{isOpen && (
<>
<motion.div
className="modal-backdrop"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
/>
<motion.div
className="modal"
initial={{ opacity: 0, scale: 0.96, y: 8 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.96, y: 8 }}
transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
>
{children}
</motion.div>
</>
)}
</AnimatePresence>
);
}
Toast:
import { motion } from 'framer-motion';
export function Toast({ message, onDismiss }) {
return (
<motion.div
layout
initial={{ opacity: 0, y: -16, scale: 0.96 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.15 } }}
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
className="toast"
>
<span>{message}</span>
<button onClick={onDismiss}>×</button>
</motion.div>
);
}
Vanilla CSS
Dropdown:
<details class="dropdown">
<summary>Menu</summary>
<div class="dropdown-content">
<a href="#">Item 1</a>
<a href="#">Item 2</a>
</div>
</details>
.dropdown-content {
overflow: hidden;
max-height: 0;
opacity: 0;
transition: max-height 300ms cubic-bezier(0.16, 1, 0.3, 1),
opacity 200ms ease-out;
}
.dropdown[open] .dropdown-content {
max-height: 300px;
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.dropdown-content {
transition: none;
}
}
Audit Report Output
HTML Report (default)
Writes to motion-audits/report-YYYYMMDD-HHMMSS.html:
- Auto-looping CSS demos beside each finding
- Severity badges (Critical, Important, Minor)
- Code diffs with syntax highlighting
- Lens-specific recommendations
- One-click fixes where possible
Opens automatically in default browser.
Terminal Report
Pass --terminal flag for inline markdown:
## Motion Design Audit
**Project**: Dashboard App
**Date**: 2026-05-28
### Reconnaissance
- **Project type**: Productivity tool (SaaS dashboard)
- **Existing animation**: Framer Motion, 250ms avg duration
- **Motion gaps found**: 7
### Proposed Lens Weighting
- **Primary**: Emil Kowalski (restraint, speed)
- **Secondary**: Jakub Krehel (polish)
- **Selective**: Jhey Tompkins (delight moments only)
### Critical Issues (2)
#### 1. Missing AnimatePresence on modal
**File**: `src/Modal.jsx:15`
**Impact**: Abrupt exit, no exit animation
**Fix**: Wrap conditional in AnimatePresence
#### 2. Hover scale on table rows
**File**: `src/Table.css:45`
**Pattern**: Hover-scale-on-everything (AI slop)
**Fix**: Remove or reduce to scale(1.01)
### Important Issues (3)
...
Troubleshooting
"Skill doesn't detect mode"
Be explicit:
"Run audit mode on this component"
"Use create mode to build an animated modal"
"Lens weighting doesn't match my project"
Override during discovery:
"Audit this, but weight Jhey primary for playful style"
"HTML report doesn't open"
Use terminal mode:
"Audit with --terminal flag"
Or manually open from motion-audits/.
"Motion feels too slow/fast"
Specify during creation:
"Build with fast, minimal animations (Emil style)"
"Add polished, 300ms animations (Jakub style)"
"Missing reduced motion support"
The skill auto-includes this. If missing, report:
"Add prefers-reduced-motion to this component"
Example Workflows
Build Animated Notification System
User: "Build an animated toast notification system for this dashboard app"
Skill:
1. Detects CREATE mode
2. Runs discovery → Dashboard = Emil primary
3. Generates:
- Toast component (React + Framer Motion)
- Fast enter/exit (150ms)
- Stack management
- Reduced motion support
- Auto-dismiss with pause-on-hover
Audit Marketing Site
User: "Audit motion design in this marketing site"
Skill:
1. Detects AUDIT mode
2. Reconnaissance → Marketing site = Jhey primary
3. Scans for:
- Motion gaps (hero CTA, feature cards)
- AI slop patterns (found 3 blur entrances)
- Missing accessibility
4. Outputs HTML report with visual demos
Configuration
No configuration file needed. All behavior is context-driven.
Environment variables:
# Optional: Skip browser open for HTML reports
MOTION_AUDIT_NO_BROWSER=1
# Optional: Default to terminal reports
MOTION_AUDIT_TERMINAL=1
References
The skill distills principles from:
- Emil Kowalski: emilkowal.ski, animations.dev
- Jakub Krehel: krehel.com
- Jhey Tompkins: jhey.dev
This skill is an interpretation, not endorsed by the designers above.
License
MIT
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/reason-machines/design-skills/design-motion-principles">View design-motion-principles on skillZs</a>