How to Build Animations from Scratch Using the 'animate' Skill
The animate skill is a construction-focused design tool that transforms motion requests into production-ready code by enforcing a disciplined 7-step decision sequence covering necessity, purpose, tool selection, GPU properties, and accessibility gates.
The animate skill in the emilkowalski/skills repository provides a strict framework for creating performant, accessible animations. It guides developers through a decision-making process documented in skills/animate/SKILL.md to ensure every animation serves a specific UX goal while respecting performance limits and Emil Kowalski’s animation philosophy.
The 7-Step Animation Decision Framework
Before writing any code, the skill requires answering seven specific questions. This sequence prevents unnecessary motion and guarantees technical correctness according to the source code analysis.
1. Determine If Animation Is Necessary
Use the frequency table to decide motion requirements: keyboard shortcuts require no animation; occasional UI elements use standard animation; rare first-time interactions may include delight animations. This gate prevents performance degradation and accessibility harm from superfluous motion.
2. Define the Single Purpose
Select exactly one purpose keyword from the approved taxonomy: feedback, spatial consistency, state indication, jarring-change prevention, explanation, or delight. This constraint guarantees the animation has a clear, measurable UX goal rather than decorative fluff.
3. Select the Cheapest Tool That Works
Choose technologies in this strict order of preference: CSS transitions first, then CSS @starting-style, then CSS animations, then the Web Animations API (element.animate), and finally Motion (motion.dev) only when necessary. This prioritizes GPU-accelerated paths and minimizes bundle size.
4. Restrict Properties to GPU-Fast Channels
Animate only transform and opacity. Avoid layout-changing properties entirely; use clip-path only when necessary for specific effects like hold-to-confirm patterns. This ensures smooth 60fps rendering on the compositor thread even under main-thread load.
5. Apply Curved Easing and Duration Tokens
Reference the curated token table for easing curves (--ease-out, --ease-in-out, --ease-drawer) and the duration matrix: buttons require 100-160ms, dropdowns 150-250ms, and modals 200-500ms. These tokens maintain consistent feel across products and avoid weak browser defaults like ease-in.
6. Handle Interruption and Exit States
Use CSS transitions for rapidly retriggered UI to ensure interruptibility. Use springs for gesture-driven interfaces. Always ensure exit animations mirror entrance animations to prevent jarring resets and maintain spatial continuity.
7. Implement Reduced-Motion and Pointer Gating
Wrap every animation in @media (prefers-reduced-motion: reduce) and @media (hover: hover) and (pointer: fine) guards. This guarantees accessibility for motion-sensitive users and eliminates false hover triggers on touch devices.
Production-Ready Code Examples from RECIPES.md
The skills/animate/RECIPES.md file provides copy-paste implementations for common UI patterns. Each recipe follows the strict decision framework above.
Button Press Feedback
For instant tactile feedback on press:
.button {
transition: transform 160ms var(--ease-out);
}
.button:active {
transform: scale(0.97);
}
- Purpose: feedback
- Tool: CSS transition
- Properties:
transform - Duration: 160ms with
--ease-out
Dropdown and Popover Scaling
For anchored overlays that maintain spatial context:
.popover {
transform-origin: var(--transform-origin);
transition:
opacity 200ms var(--ease-out),
transform 200ms var(--ease-out);
}
.popover[data-starting-style],
.popover[data-ending-style] {
opacity: 0;
transform: scale(0.95);
}
- Purpose: spatial consistency
- Tool: CSS transition with anchored origin
Modal Entrance
For centered focus-layer animations:
.modal {
transform-origin: center;
transition:
opacity 250ms var(--ease-out),
transform 250ms var(--ease-out);
}
.modal[data-starting-style],
.modal[data-ending-style] {
opacity: 0;
transform: scale(0.96);
}
.backdrop {
transition: opacity 250ms var(--ease-out);
}
- Purpose: delight or focus
Drawer Sheet Transitions
For iOS-style off-canvas panels:
.drawer {
transform: translateY(0);
transition: transform 500ms var(--ease-drawer);
}
.drawer[data-closed] {
transform: translateY(100%);
}
- Purpose: spatial consistency
- Note: Uses layout-independent
translateYfor compositor-only animation
Hold-to-Confirm Destructive Actions
Use clip-path for deliberate, time-based confirmation:
.overlay {
clip-path: inset(0 100% 0 0);
transition: clip-path 200ms var(--ease-out);
}
.button:active .overlay {
clip-path: inset(0 0 0 0);
transition: clip-path 2s linear;
}
.button:active { transform: scale(0.97); }
- Purpose: preventing a jarring change
Gesture-Driven Dismissal
For drag-to-dismiss with physics-based feel:
const handleDragEnd = (e) => {
const timeTaken = Date.now() - dragStartTime.current;
const velocity = Math.abs(swipeAmount) / timeTaken;
if (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {
element.animate(
[{ transform: `translateY(${distance}px)` }, { transform: 'translateY(100%)' }],
{ duration: 500, easing: 'cubic-bezier(0.23,1,0.32,1)', fill: 'forwards' }
);
}
};
- Purpose: delight or gesture-driven
- Tool: Web Animations API (WAAPI)
Required Accessibility Wrappers
Every implementation must include these guards as specified in section 7 of SKILL.md:
@media (prefers-reduced-motion: reduce) {
.animated-element {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}
@media (hover: hover) and (pointer: fine) {
.hover-trigger {
/* hover animations only */
}
}
Output Format and Review
After implementation, the skill requires documenting:
- The gate result (frequency tier and purpose keyword)
- The chosen ingredients (tool, properties, easing curve, duration)
- Any feel-check notes regarding interruptibility or performance
This documentation enables the companion review-animations skill to validate the code against the strict output contract defined in the repository.
Summary
- The animate skill enforces a 7-step decision sequence before coding begins, documented in
skills/animate/SKILL.md - Tool selection hierarchy: Prefer CSS transitions, then
@starting-style, then CSS animations, then WAAPI, then Motion - Property restriction: Use only
transformandopacityfor GPU acceleration; avoid layout properties - Token system: Apply curated easing curves (
--ease-out,--ease-drawer) and duration matrices (100-500ms depending on component) - Mandatory guards: Wrap all animations in
prefers-reduced-motionand hover/pointer media queries - Reference implementations: Copy patterns from
skills/animate/RECIPES.mdfor buttons, dropdowns, modals, drawers, and gestures
Frequently Asked Questions
What is the cheapest animation tool recommended by the animate skill?
The skill prioritizes CSS transitions as the cheapest tool that works for most UI patterns. If transitions are insufficient for complex sequences, escalate to CSS @starting-style, then CSS animations, then the Web Animations API (element.animate), and only use Motion (motion.dev) as a last resort to minimize bundle size and maximize GPU efficiency.
Why does the animate skill restrict animations to transform and opacity?
These are the only properties that guarantee compositor-only animations running at 60fps. Animating layout properties like width, height, or top triggers main-thread layout recalculations, causing jank under load. The skill allows clip-path only for specific patterns like hold-to-confirm buttons where visual clipping is required.
How do I handle users who prefer reduced motion?
Every animation must be wrapped in @media (prefers-reduced-motion: reduce) with immediate completion (0.01ms duration) or instant state changes. This is a hard requirement in step 7 of the skill's decision framework, ensuring accessibility for users with vestibular disorders.
Where can I find ready-made code examples for common components?
Production-ready implementations for buttons, dropdowns, modals, drawers, toasts, and gestures are available in skills/animate/RECIPES.md within the emilkowalski/skills repository. Each recipe includes the specific purpose keyword, tool choice, and property set required by the skill's review gates.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →