dist/plugins/web-animation-css-animations/skills/web-animation-css-animations/SKILL.md
CSS Animation patterns - transitions, keyframes, scroll-driven animations, @property, GPU-accelerated properties, accessibility with prefers-reduced-motion
npx skillsauth add agents-inc/skills web-animation-css-animationsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use CSS transitions for state changes (hover, focus),
@keyframesfor autonomous/looping animations, scroll-driven animations for scroll-linked effects. Animate onlytransformandopacityfor 60fps. Always respectprefers-reduced-motion.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST animate ONLY transform and opacity for GPU-accelerated 60fps performance)
(You MUST respect prefers-reduced-motion using @media (prefers-reduced-motion: no-preference) for opt-in or @media (prefers-reduced-motion: reduce) for opt-out)
(You MUST use CSS custom properties for ALL timing values - NO magic numbers like 0.3s)
(You MUST use ease-out for enter animations and ease-in for exit animations - NEVER linear for UI transitions)
(You MUST remove will-change after animation completes - permanent will-change wastes GPU memory)
</critical_requirements>
Auto-detection: CSS animation, CSS transition, @keyframes, transform, opacity, transition-duration, animation-duration, prefers-reduced-motion, scroll-timeline, animation-timeline, will-change, cubic-bezier, ease-out, ease-in, @property
When to use:
When NOT to use:
Detailed Resources:
CSS animations leverage the browser's compositor thread for smooth, 60fps animations that don't block JavaScript execution. By animating only GPU-accelerated properties (transform and opacity), animations run on a separate thread from the main JavaScript thread.
Core principles:
transform and opacity to avoid layout/paint triggersprefers-reduced-motion user preferences@keyframes for animations that loop, auto-play, or have multiple stepsDefine timing, easing, and distance tokens as CSS custom properties for consistency. See examples/core.md for the full token setup.
:root {
--duration-instant: 100ms;
--duration-fast: 150ms;
--duration-normal: 250ms;
--duration-slow: 400ms;
--ease-out: cubic-bezier(0, 0, 0.2, 1); /* Enter */
--ease-in: cubic-bezier(0.4, 0, 1, 1); /* Exit */
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); /* Symmetric */
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275); /* Bouncy */
--lift-sm: -2px;
--lift-md: -4px;
}
Why tokens matter: Consistent timing across application, easy to adjust globally, semantic naming communicates intent
Only animate transform and opacity. Never animate layout properties like width, height, top, left, margin, or padding.
/* CORRECT - GPU-accelerated */
.card {
transition:
transform var(--duration-fast) var(--ease-out),
opacity var(--duration-fast) var(--ease-out);
}
.card:hover {
transform: translateY(var(--lift-md)) scale(1.02);
}
/* WRONG - triggers layout recalculation every frame */
.card {
transition: all 0.3s linear;
}
.card:hover {
top: -8px;
margin-top: -8px;
}
Transform mapping: Use translate() instead of top/left, scale() instead of width/height, pseudo-element opacity instead of box-shadow.
See examples/core.md for button states, card hover effects, and the pseudo-element shadow technique.
Every animation must respect user motion preferences. Two strategies:
/* Base: no motion */
.element {
opacity: 1;
transform: translateY(0);
}
/* Opt-in to motion */
@media (prefers-reduced-motion: no-preference) {
.element {
animation: fade-slide-in var(--duration-normal) var(--ease-out);
}
}
.notification {
animation: slide-in-bounce var(--notification-duration) var(--ease-spring);
}
@media (prefers-reduced-motion: reduce) {
.notification {
animation: fade-in calc(var(--notification-duration) * 0.5) var(--ease-out);
}
}
Key insight: Reduced motion does not mean no animation. Opacity fades are generally safe. Replace spatial movement with opacity-only alternatives.
See examples/core.md for the complete reduced motion pattern.
Use @keyframes for animations that loop, auto-play on mount, or have more than two states.
@keyframes fade-slide-in {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal {
--modal-enter-duration: 300ms;
animation: fade-slide-in var(--modal-enter-duration) var(--ease-out) forwards;
}
Key details:
forwards fill mode to retain final state after animationbackwards fill mode to show initial state during animation-delayease-out for enter, ease-in for exitlinear is only appropriate for continuous rotation (spinners)See examples/core.md for spinners, pulses, skeleton loaders, and toast animations. See examples/keyframes.md for scroll-driven animations, @property gradients, and complex sequences.
will-change creates a GPU layer (~307KB per 320x240px element). Apply only when needed, remove after.
/* CORRECT - only during interaction */
.card:hover {
will-change: transform;
}
/* WRONG - permanent GPU layer on every element */
* {
will-change: transform;
}
Never apply will-change permanently. Each element with will-change creates a separate compositing layer that consumes GPU memory. On mobile devices, this can crash the browser.
CSS animation-timeline allows scroll-linked animations without JavaScript.
.progress-bar {
animation: grow-width linear;
animation-timeline: scroll();
}
@keyframes grow-width {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
Two timeline types:
scroll() -- progress based on scroll container positionview() -- progress based on element visibility in viewportBrowser support: Chrome/Edge 115+, Safari 26+, Firefox behind flag
See examples/keyframes.md for scroll progress, viewport reveal, and parallax patterns.
CSS Houdini's @property enables animating custom properties like gradient angles that CSS cannot normally interpolate.
@property --gradient-angle {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
.gradient-border {
background: linear-gradient(var(--gradient-angle), #ff0080, #7928ca);
animation: rotate-gradient 3s linear infinite;
}
@keyframes rotate-gradient {
to {
--gradient-angle: 360deg;
}
}
Browser support: Chrome/Edge 85+, Safari 16.4+, Firefox 128+
</patterns>For 60fps, each frame must complete in 16.67ms. Layout-triggering animations often exceed this budget.
| Category | Properties | Impact | | -------------------------- | ----------------------------------------- | ------------------------------------ | | Composite only (Best) | transform, opacity | No layout, no paint, GPU-accelerated | | Paint only (Okay) | color, background-color, visibility | No layout, but repaints | | Layout + Paint (Avoid) | width, height, margin, padding, top, left | Full page recalculation |
| Animation Type | Duration | Reason | | ------------------ | ---------- | ------------------------- | | Micro-interactions | 100-150ms | Feels instant | | UI transitions | 200-300ms | Sweet spot for perception | | Page transitions | 300-500ms | Major context change | | Complex sequences | 500-1000ms | Story-telling moments |
| Instead of... | Use... |
| ------------------- | ------------------------------- |
| top, left | translate(x, y) |
| width, height | scale() |
| box-shadow | Pseudo-element with opacity |
| margin, padding | translate() with layout space |
<decision_framework>
Is the animation triggered by user interaction (hover, focus, class toggle)?
├─ YES → Is it a simple A->B state change?
│ ├─ YES -> CSS Transition
│ └─ NO -> Does it need multiple steps?
│ ├─ YES -> CSS @keyframes
│ └─ NO -> CSS Transition is fine
└─ NO -> Does it auto-play or loop?
├─ YES -> CSS @keyframes
└─ NO -> CSS Transition (triggered by class toggle)
What type of motion?
├─ Element entering -> ease-out (fast start, slow end)
├─ Element exiting -> ease-in (slow start, fast end)
├─ Symmetric motion -> ease-in-out
├─ Continuous rotation -> linear
├─ Playful/bouncy -> custom cubic-bezier with overshoot
└─ Default UI -> ease-out
Never use:
├─ linear for UI transitions (feels robotic)
└─ ease (browser default) for production (too generic)
Does the animation need...
├─ Pause/play/reverse/seek control? -> JavaScript (Web Animations API)
├─ Dynamic values calculated at runtime? -> JavaScript or CSS custom properties
├─ Physics-based springs? -> Your animation library
├─ Orchestrated staggering across many elements? -> JavaScript for complex, CSS for simple
├─ Scroll-linked progress? -> CSS scroll-driven animations
├─ Page/view transitions? -> See the View Transitions skill
└─ Simple state transitions? -> CSS Transitions
</decision_framework>
<red_flags>
width, height, top, left, margin, padding) -- triggers expensive reflows every frame; use transform instead0.3s, 300ms inline) -- all durations must be CSS custom propertiesprefers-reduced-motion -- every animation must respect user preferenceslinear feels robotic; use ease-out for enter, ease-in for exitwill-change -- creates GPU layers permanently, wasting memory; apply only during animationtransition: all -- transitions unnecessary properties, causes surprises when new properties are addedbox-shadow directly -- causes repaint every frame; use pseudo-element with opacityforwards on enter animations -- element snaps back to initial statetransform + position: fixed -- transform creates new containing block, breaking fixed positioning relative to viewportwill-change creates stacking context -- can affect z-index behavior unexpectedlydisplay: none -- use opacity + visibility or grid-template-rows: 0frfill-mode: backwards needed for delayed animations -- without it, element shows in final state during delaystroke-dashoffset and stroke-dasharray, not transform for path drawingoverflow: hidden parent breaks scroll-timeline</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST animate ONLY transform and opacity for GPU-accelerated 60fps performance)
(You MUST respect prefers-reduced-motion using @media (prefers-reduced-motion: no-preference) for opt-in or @media (prefers-reduced-motion: reduce) for opt-out)
(You MUST use CSS custom properties for ALL timing values - NO magic numbers like 0.3s)
(You MUST use ease-out for enter animations and ease-in for exit animations - NEVER linear for UI transitions)
(You MUST remove will-change after animation completes - permanent will-change wastes GPU memory)
Failure to follow these rules will cause jank, accessibility issues, and degraded user experience.
</critical_reminders>
development
Material Design component library for Vue 3
development
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
tools
Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment
development
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety