animate
Animate — CSS-First On-Page Animation Skill
Generate production-quality on-page animations for React + Next.js + Tailwind sites. The core principle is CSS-first, Motion-surgical — use pure CSS and IntersectionObserver for 80-90% of animations, reach for Motion (Framer Motion) only when CSS genuinely cannot solve the problem.
Why CSS-first:
- Zero bundle cost (CSS is free, Motion adds JS to the client bundle)
- No
"use client"boundary required (preserves React Server Components) - Compositor-thread animation (transform/opacity) — better performance than main-thread JS
- 4/10 best-in-class SaaS sites (Linear, Stripe, Resend, Liveblocks) use zero JS animation libraries
- The practitioner consensus: Emil Kowalski, Stripe engineering, WordPress Gutenberg all advocate CSS as default
Motion is irreplaceable for exactly 6 patterns:
- Exit animations on React unmount (
AnimatePresence) - Shared element transitions (
layoutId) - Gesture-driven animations (drag, swipe, pinch)
- Automatic layout animations (FLIP for size/position changes)
- Complex variant orchestration (parent→child cascading timing)
- Animating to
height: auto(accordion expand/collapse) — CSS cannot interpolate toheight: auto. The emerginginterpolate-size: allow-keywords(Chrome 129+) will solve this but is not yet Baseline.
If the animation doesn't require one of these five, use CSS.
Workflow
Create workflow tasks (first action)
Before starting any work, create a task for each step using TaskCreate with addBlockedBy to enforce ordering. Derive descriptions and completion criteria from each step's own workflow text.
- Animate: Understand request and run tier decision
- Animate: Discover project conventions
- Animate: Generate component
- Animate: Verify output
Mark each task in_progress when starting and completed when its step's exit criteria are met. On re-entry, check TaskList first and resume from the first non-completed task.
Step 1: Understand the request
Identify:
- What type of animation? Entrance, scroll-triggered, hover, interactive, looping demo, background effect
- Where does it go? New component, existing component, page section
- Does it need to be interactive? Hover only, click-triggered, scroll-driven, gesture-driven
- Does anything need to animate OUT (unmount)? This is the key Motion trigger
Step 2: Run the tier decision
Walk through this flowchart. Stop at the first YES.
Is the animation purely CSS-driven (hover, focus, keyframes, scroll-timeline)?
├── YES → Tier 1: Pure CSS
│ Load: references/css-patterns.md
└── NO
├── Does the element need to animate on React unmount?
│ └── YES → Tier 4: Motion (AnimatePresence)
│ Load: references/motion-patterns.md
├── Does the element need to animate to a new DOM position?
│ └── YES → Tier 4: Motion (layoutId)
│ Load: references/motion-patterns.md
├── Does the user interact via drag/swipe/pinch?
│ └── YES → Tier 4: Motion (gestures)
│ Load: references/motion-patterns.md
├── Does the animation need scroll-trigger visibility detection?
│ └── YES → Tier 2: CSS + IntersectionObserver
│ Load: references/css-patterns.md
└── Is the animation a simple entrance triggered by mount?
└── YES → Tier 2: CSS + IntersectionObserver
Load: references/css-patterns.md
When in doubt, default to Tier 2 (CSS + IntersectionObserver). This covers the vast majority of marketing site animations.
If the request is specifically a product demo animation (showing software UI in action, cursor movements, screen transitions), also Load: references/product-demo-patterns.md
If the built-in patterns don't cover the requested animation type, Load: references/inspiration-repos.md — OSS repos with inspectable source code for unusual effects.
Step 3: Discover project conventions
Before generating code, scan the target codebase for existing animation patterns:
-
Check for Motion usage:
grep -r "from.*motion" src/ --include="*.tsx" | head -5- Note which components use Motion and what features they use (AnimatePresence, layoutId, drag, or just whileInView)
- Simple
whileInViewfade-ups are candidates for CSS replacement
-
Check for existing easing/duration conventions:
- Search for
ease:ortransition:patterns in existing animated components - Match whatever easing and duration the project already uses
- If no conventions exist, use these defaults:
- Easing:
ease-out(CSS) or[0.25, 0.46, 0.45, 0.94](Motion cubic-bezier) - Duration (marketing entrance reveals):
500msfor fade-ups,300msfor hover/micro-interactions,600-800msfor hero reveals - Duration (interactive UI — buttons, dropdowns, modals, tooltips): cap at
150-300msper Nielsen Norman Group and Material Design guidelines. The 500ms entrance default is for scroll-triggered reveals seen once, not repeated interactions. - Stagger:
80msbetween items. Total stagger time should stay under 500ms — scale per-item delay inversely with item count: 80ms for 4 items, 50ms for 8 items, 25ms for 16+ items. - Entrance translate:
20-30pxupward (translateY(20px)→translateY(0)) — use fixed pixels for fade-up entrances (visual consistency across element sizes). UsetranslateY(100%)(percentages) only for off-screen positioning (toasts, drawers, sheets sliding fully in/out).
- Easing:
- Search for
-
Check Tailwind version. Tailwind v4 has breaking changes for animations:
transition-[opacity,transform]does NOT work — v4 uses individual properties. Usetransition-[opacity,translate,scale,rotate]instead.blur-smis 8px in v4 (was 4px in v3). Useblur-xsfor the oldblur-smbehavior.- v4 adds native 3D:
perspective-*,rotate-x-*,rotate-y-*,translate-z-*,transform-3d - v4 adds
starting:variant (maps to@starting-style) andtransition-discrete(maps totransition-behavior: allow-discrete) - Custom animations use
@themein CSS instead oftailwind.config.js:@theme { --animate-fade-up: fade-up 0.5s ease-out; } @keyframes fade-up { from { opacity: 0; transform: translateY(20px); } }
Step 4: Generate the component
Load the appropriate reference file and generate the component. Follow these rules for ALL output:
Load: references/performance-rules.md — always, for every animation.
Component conventions:
- Add
"use client"only if the component uses React hooks or Motion. CSS-only animations with no JS interaction do NOT need it. - Use Tailwind classes for animation properties when they exist (
transition-all,duration-500,ease-out). Fall back to inline styles or@keyframesin a<style>tag or CSS module for complex sequences. - For IntersectionObserver, prefer
react-intersection-observerif already in the project, otherwise use the native API with a custom hook.
Step 5: Verify output
Before delivering, check:
- Only compositor-safe properties animated (transform, opacity, filter, clip-path)
-
prefers-reduced-motionhandled via no-motion-first pattern (spatial motion opt-in; opacity/color transitions always active) -
"use client"added only when necessary - No unnecessary Motion imports (if CSS solves the problem, don't import motion)
- Entrance animations use
once: true(don't replay on every scroll) - Component works without JavaScript (CSS animations degrade gracefully)
Quick Reference — Animation Types
| Animation needed | Tier | Approach |
|---|---|---|
| Fade-up on scroll | 2 | CSS transition + IntersectionObserver class toggle |
| Hover card lift | 1 | CSS transition + :hover pseudo-class |
| Staggered grid entrance | 2 | CSS animation-delay via custom property + IntersectionObserver |
| Hero perspective reveal | 1-2 | CSS @keyframes with rotateX + perspective container |
| SVG line draw | 1 | CSS stroke-dashoffset animation |
| Background gradient | 1 | CSS @property + @keyframes |
| Tab content swap with exit | 4 | Motion AnimatePresence mode="wait" |
| Animated tab indicator | 4 | Motion layoutId |
| Modal/dropdown close | 4 | Motion AnimatePresence |
| Drag-to-reorder | 4 | Motion drag + Reorder |
| Scroll-linked parallax | 1 | CSS animation-timeline: view() |
| Product demo (UI showcase) | 2 | CSS + IntersectionObserver + product demo patterns |
| Looping product demo | 2-4 | State machine with CSS transitions (or useAnimationFrame for complex sequences) |
| Clip-path reveal | 1 | CSS clip-path transition |
| Animated counter | 1 | CSS @property with <integer> syntax |
Common Mistakes
❌ Using motion.div for a simple fade-up entrance
✅ CSS class toggle via IntersectionObserver — avoids adding "use client" boundary and bundle cost
❌ Animating width, height, top, left, margin, padding
✅ Animating transform, opacity, filter, clip-path — compositor-safe
❌ Missing prefers-reduced-motion handling
✅ No-motion-first: spatial motion inside @media (prefers-reduced-motion: no-preference); opacity/color always active
❌ Global * { animation-duration: 0.01ms !important } — kills helpful opacity/color transitions, prevents per-component overrides, can cause JS animation libraries to complete instantly
✅ Each component controls its own reduced-motion behavior — keep opacity fades, remove transform-based movement
❌ Forgetting "use client" on a component that uses Motion hooks
✅ Add "use client" at the top when using motion/react imports
❌ Adding "use client" to a component that only uses CSS animations
✅ CSS animations work in Server Components — no "use client" needed
❌ Entrance animations that replay every time element scrolls in/out of view
✅ Use triggerOnce: true (react-intersection-observer) or observe once then disconnect
❌ AnimatePresence wrapping an element that's always in the DOM
✅ AnimatePresence is only for elements that mount/unmount ({show && <Component />}).
For class-toggle visibility, use CSS transition-behavior: allow-discrete instead.
❌ Using Motion for scroll-linked parallax when page has no other Motion usage
✅ CSS animation-timeline: view() with @supports fallback — runs on compositor thread
❌ Wrapping an entire page component in "use client" just for one animation
✅ Extract only the animated wrapper as a Client Component; pass content as children
❌ Using Motion for animation that coexists with heavy JS work (route changes, data loading)
✅ Use CSS transitions — they run on the compositor thread and are unaffected by main thread load.
Documented production issue: Vercel replaced Motion shared layout animations with CSS because
Motion dropped frames during page transitions when the main thread was busy loading the new page.
❌ Defining the same @keyframes in multiple CSS files across the project
✅ Define animation tokens (duration, easing, keyframes) once in globals.css, reference via custom properties
❌ Putting <LazyMotion> at the app root level
✅ Wrap LazyMotion around only the specific component that needs Motion — root-level causes subtree re-renders
Cross-References
- Need a video version of the animation? Use the Remotion video pipeline (
/motion-video,/video-pipeline,/blog-to-video) — same visual concept, rendered to MP4 for social distribution. - Need a static graphic instead? Use
/graphics— Figma designs, AI-generated images, 3D renders. - Need an interactive 3D element? Consider React Three Fiber (
@react-three/fiber) for WebGL content, or Rive for interactive 2D/2.5D.
More from inkeep/team-skills
spec
Drive an evidence-driven, iterative product+engineering spec process that produces a full PRD + technical spec (often as SPEC.md). Use when scoping a feature or product surface area end-to-end; defining requirements; researching external/internal prior art; mapping current system behavior; comparing design options; making 1-way-door decisions; negotiating scope; and maintaining a live Decision Log + Open Questions backlog. Triggers: spec, PRD, proposal, technical spec, RFC, scope this, design doc, end-to-end requirements, scope plan, tradeoffs, open questions.
54docs
Write or update documentation for engineering changes — both product-facing (user docs, API reference, guides) and internal (architecture docs, runbooks, inline code docs). Builds a world model of what changed and traces transitive documentation consequences across all affected surfaces. Discovers and uses repo-specific documentation skills, style guides, and conventions. Standalone or composable with /ship. Triggers: docs, documentation, write docs, update docs, document the changes, product docs, internal docs, changelog, migration guide.
52write-agent
Design and write high-quality Claude Code agents and agent prompts. Use when creating or updating .claude/agents/*.md for (1) single-purpose subagents (reviewers, implementers, researchers) and (2) workflow orchestrators (multi-phase coordinators like pr-review, feature-development, bug-fix). Covers delegation triggers, tool/permission/model choices, Task-tool orchestration, phase handoffs, aggregation, iteration gates, and output contracts. Also use when deciding between subagents vs skills vs always-on repo guidance.
50browser
Browser automation with two engines: agent-browser CLI (default for interactive work — navigate, snapshot, click, fill, screenshot, diff) and Playwright scripts (for compound operations — console/network capture, a11y audits, video recording, responsive sweeps, GIF pipeline, shadow DOM piercing). Triggers: browser automation, web test, screenshot, responsive test, test the page, automate browser, headless browser, UI test, console errors, network inspection, accessibility audit, a11y test, performance metrics, video recording, browser state, page structure, authenticated testing, annotated gif, dialog handling, tracing, pdf generation, shadow DOM, web components, DOM stabilization.
49tdd
|
48write-skill
Create or revise Claude Code-compatible Agent Skills (SKILL.md with optional references/, scripts/, and assets/). Use when designing a new skill, improving an existing skill, or updating/refactoring an existing skill while preserving the original author's intent (avoid semantic drift unless explicitly requested/approved by the author). Also use when integrating skills with subagents (context fork, agent).
45