Overview
Produces high-quality, performant CSS animations and transitions using modern best practices. The skill generates keyframe definitions, timing functions, accessibility-respecting motion (prefers-reduced-motion), GPU-accelerated properties, stagger patterns, scroll-driven animations, and ready-to-use recipes for common UI motions such as fades, slides, bounces, loading states, and hover effects.
When to Use This Skill
- Adding subtle motion to buttons, cards, modals, loaders, or page transitions.
- The user explicitly requests "CSS animation", "keyframe", "transition", "micro-interaction", or "motion".
- Avoiding JavaScript animation libraries (Framer Motion, GSAP, anime.js) for performance or bundle size reasons.
- Implementing loading states, success/error feedback, or attention-guiding animations.
- Creating scroll-triggered or entrance animations.
Prerequisites
- A web project using CSS (plain CSS, Tailwind, SCSS, etc.).
- Target browsers that support modern CSS (Chrome 90+, Firefox 90+, Safari 15+ recommended for advanced features like
@property or scroll timelines).
- For scroll-driven animations, the page must have scrollable content.
- No JavaScript required unless the skill explicitly adds a tiny progressive enhancement script.
Steps
Analyze the motion requirement:
- Identify the trigger (hover, focus, load, click, scroll, state change).
- Determine the animation type: entrance, exit, attention, feedback, loading, or continuous.
- Choose properties that are GPU-composited:
transform (translate, scale, rotate), opacity, filter. Avoid animating width, height, left, top, margin, padding when possible.
Respect user motion preferences (mandatory):
- Always wrap animations in
@media (prefers-reduced-motion: no-preference) { ... }.
- Provide a reduced or static fallback for users who prefer reduced motion.
- Never animate on load for users with reduced motion unless it is essential.
Select timing and easing:
- Use
cubic-bezier() for custom easings (e.g., cubic-bezier(0.4, 0, 0.2, 1) for material design).
- Common durations: 150ms (micro), 250-300ms (standard), 400-600ms (emphasis), 800ms+ (page-level).
- Use
animation-fill-mode: both or forwards/backwards as needed.
- Prefer
transition for simple state changes; use @keyframes + animation for complex or repeating sequences.
Write the keyframes:
- Name them descriptively (e.g.,
fadeInUp, pulse, modalEnter).
- Use percentage-based or
from/to.
- Keep keyframe lists short (3-5 stops maximum for most UI work).
Implement stagger for lists:
- Use
animation-delay with incremental values (e.g., calc(var(--i) * 80ms)).
- Set
--i via inline style or CSS counters on list items.
Add scroll-driven animations (modern):
- Use
animation-timeline: view() or scroll() when supported.
- Provide a JavaScript fallback using IntersectionObserver for older browsers.
Provide multiple recipe variants:
- Always deliver the animation in a self-contained code block.
- Show how to apply it to real HTML elements.
- Include Tailwind arbitrary value versions when relevant.
Include performance and accessibility notes:
- List which properties are safe.
- Warn about
will-change usage (use sparingly).
- Document how the animation respects reduced motion.
Output complete, copy-pasteable code:
- Include the CSS (and minimal HTML demo if helpful).
- Add comments explaining key decisions.
Examples
Example 1: Fade + Slide Up Entrance Animation (with stagger)
/* Fade In Up Animation */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: no-preference) {
.fade-in-up {
animation: fadeInUp 400ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
/* Staggered list items */
.stagger-list > * {
--stagger-delay: 80ms;
animation-delay: calc(var(--i, 0) * var(--stagger-delay));
}
}
/* Usage */
<div class="stagger-list">
<div class="fade-in-up" style="--i: 0">Item 1</div>
<div class="fade-in-up" style="--i: 1">Item 2</div>
<div class="fade-in-up" style="--i: 2">Item 3</div>
</div>
Example 2: Subtle Button Micro-interaction (Scale + Shadow)
@media (prefers-reduced-motion: no-preference) {
.btn-primary {
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-primary:hover {
transform: translateY(-1px) scale(1.02);
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
}
.btn-primary:active {
transform: translateY(0) scale(0.985);
transition-duration: 75ms;
}
}
Example 3: Loading Spinner (Infinite, GPU only)
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: no-preference) {
.spinner {
width: 24px;
height: 24px;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 9999px;
animation: spin 800ms linear infinite;
}
}
Example 4: Scroll-Driven Fade (with fallback)
/* Modern scroll-driven */
@media (prefers-reduced-motion: no-preference) and (prefers-reduced-motion: no-preference) {
@supports (animation-timeline: view()) {
.scroll-fade {
animation: fadeIn linear both;
animation-timeline: view();
animation-range: entry 0% entry 50%;
}
}
}
/* JS fallback for older browsers */
.scroll-fade {
opacity: 0;
transform: translateY(20px);
transition: opacity 400ms ease, transform 400ms ease;
}
.scroll-fade.is-visible {
opacity: 1;
transform: none;
}
(Include a small IntersectionObserver script in the output when using the fallback.)
Example 5: Attention Pulse (for notifications)
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
@media (prefers-reduced-motion: no-preference) {
.attention-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
}
Edge Cases & Error Handling
- User has
prefers-reduced-motion: reduce: Always provide a no-animation path. For loading spinners that are essential, use a static indicator + ARIA live region instead of infinite animation.
- Animation causes layout thrashing: Only animate
transform and opacity. If the user requests width/height animation, provide a transform: scale alternative and explain the performance difference.
- Too many staggered items: Cap stagger at ~10-12 items or use a CSS variable that the agent can adjust. Suggest IntersectionObserver + class toggle for very long lists.
- Browser support: For advanced features (scroll timelines,
@property), always provide a progressive enhancement comment and a basic fallback.
- Accessibility: Never use animation to convey critical information without a static alternative. For attention animations, also use color or icon changes.
- Performance on low-end devices: Recommend testing with DevTools throttling. Suggest
will-change: transform only on the animating element and remove it after the animation ends.
- Conflict with Tailwind: Show how to use arbitrary values (
animate-[spin_800ms_linear_infinite]) and how to extend Tailwind config with custom keyframes.
Verification
- Paste the generated CSS + HTML into a test file and open in Chrome, Firefox, and Safari.
- Reduced motion test: Set
prefers-reduced-motion: reduce in DevTools (Rendering panel) — confirm animations are disabled or greatly simplified.
- Performance check: Use Chrome DevTools Performance tab while triggering the animation. Confirm no layout thrashing (purple bars minimal) and GPU compositing (green bars for the layer).
- Accessibility: Use keyboard to trigger hover/focus states. Verify focus is never lost due to animation. Run axe or Lighthouse — motion-related issues should be absent.
- Stagger verification: Inspect computed
animation-delay values on list items; they should increment correctly.
- Success: Animations are smooth (60fps), respect user preferences, use only safe properties, and enhance (not distract from) the UI.
References
1---2name: css-animation-crafter3description: Creates CSS keyframe animations, transitions, and micro-interactions. Use when adding motion to web UIs without JavaScript animation libraries.4license: Apache-2.05compatibility: Claude Code, Codex, Gemini CLI6metadata:7 author: ai-skills8 version: "1.0"9 category: web-development10 tags: css, animation, keyframes, transitions, accessibility, micro-interactions11---1213## Overview1415Produces high-quality, performant CSS animations and transitions using modern best practices. The skill generates keyframe definitions, timing functions, accessibility-respecting motion (prefers-reduced-motion), GPU-accelerated properties, stagger patterns, scroll-driven animations, and ready-to-use recipes for common UI motions such as fades, slides, bounces, loading states, and hover effects.1617## When to Use This Skill1819- Adding subtle motion to buttons, cards, modals, loaders, or page transitions.20- The user explicitly requests "CSS animation", "keyframe", "transition", "micro-interaction", or "motion".21- Avoiding JavaScript animation libraries (Framer Motion, GSAP, anime.js) for performance or bundle size reasons.22- Implementing loading states, success/error feedback, or attention-guiding animations.23- Creating scroll-triggered or entrance animations.2425## Prerequisites2627- A web project using CSS (plain CSS, Tailwind, SCSS, etc.).28- Target browsers that support modern CSS (Chrome 90+, Firefox 90+, Safari 15+ recommended for advanced features like `@property` or scroll timelines).29- For scroll-driven animations, the page must have scrollable content.30- No JavaScript required unless the skill explicitly adds a tiny progressive enhancement script.3132## Steps33341. **Analyze the motion requirement**:35 - Identify the trigger (hover, focus, load, click, scroll, state change).36 - Determine the animation type: entrance, exit, attention, feedback, loading, or continuous.37 - Choose properties that are GPU-composited: `transform` (translate, scale, rotate), `opacity`, `filter`. Avoid animating `width`, `height`, `left`, `top`, `margin`, `padding` when possible.38392. **Respect user motion preferences (mandatory)**:40 - Always wrap animations in `@media (prefers-reduced-motion: no-preference) { ... }`.41 - Provide a reduced or static fallback for users who prefer reduced motion.42 - Never animate on load for users with reduced motion unless it is essential.43443. **Select timing and easing**:45 - Use `cubic-bezier()` for custom easings (e.g., `cubic-bezier(0.4, 0, 0.2, 1)` for material design).46 - Common durations: 150ms (micro), 250-300ms (standard), 400-600ms (emphasis), 800ms+ (page-level).47 - Use `animation-fill-mode: both` or `forwards`/`backwards` as needed.48 - Prefer `transition` for simple state changes; use `@keyframes` + `animation` for complex or repeating sequences.49504. **Write the keyframes**:51 - Name them descriptively (e.g., `fadeInUp`, `pulse`, `modalEnter`).52 - Use percentage-based or `from`/`to`.53 - Keep keyframe lists short (3-5 stops maximum for most UI work).54555. **Implement stagger for lists**:56 - Use `animation-delay` with incremental values (e.g., `calc(var(--i) * 80ms)`).57 - Set `--i` via inline style or CSS counters on list items.58596. **Add scroll-driven animations** (modern):60 - Use `animation-timeline: view()` or `scroll()` when supported.61 - Provide a JavaScript fallback using IntersectionObserver for older browsers.62637. **Provide multiple recipe variants**:64 - Always deliver the animation in a self-contained code block.65 - Show how to apply it to real HTML elements.66 - Include Tailwind arbitrary value versions when relevant.67688. **Include performance and accessibility notes**:69 - List which properties are safe.70 - Warn about `will-change` usage (use sparingly).71 - Document how the animation respects reduced motion.72739. **Output complete, copy-pasteable code**:74 - Include the CSS (and minimal HTML demo if helpful).75 - Add comments explaining key decisions.7677## Examples7879**Example 1: Fade + Slide Up Entrance Animation (with stagger)**8081```css82/* Fade In Up Animation */83@keyframes fadeInUp {84 from {85 opacity: 0;86 transform: translateY(20px);87 }88 to {89 opacity: 1;90 transform: translateY(0);91 }92}9394@media (prefers-reduced-motion: no-preference) {95 .fade-in-up {96 animation: fadeInUp 400ms cubic-bezier(0.4, 0, 0.2, 1) both;97 }98 99 /* Staggered list items */100 .stagger-list > * {101 --stagger-delay: 80ms;102 animation-delay: calc(var(--i, 0) * var(--stagger-delay));103 }104}105106/* Usage */107<div class="stagger-list">108 <div class="fade-in-up" style="--i: 0">Item 1</div>109 <div class="fade-in-up" style="--i: 1">Item 2</div>110 <div class="fade-in-up" style="--i: 2">Item 3</div>111</div>112```113114**Example 2: Subtle Button Micro-interaction (Scale + Shadow)**115116```css117@media (prefers-reduced-motion: no-preference) {118 .btn-primary {119 transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1),120 box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1);121 }122 123 .btn-primary:hover {124 transform: translateY(-1px) scale(1.02);125 box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);126 }127 128 .btn-primary:active {129 transform: translateY(0) scale(0.985);130 transition-duration: 75ms;131 }132}133```134135**Example 3: Loading Spinner (Infinite, GPU only)**136137```css138@keyframes spin {139 to { transform: rotate(360deg); }140}141142@media (prefers-reduced-motion: no-preference) {143 .spinner {144 width: 24px;145 height: 24px;146 border: 3px solid #e5e7eb;147 border-top-color: #3b82f6;148 border-radius: 9999px;149 animation: spin 800ms linear infinite;150 }151}152```153154**Example 4: Scroll-Driven Fade (with fallback)**155156```css157/* Modern scroll-driven */158@media (prefers-reduced-motion: no-preference) and (prefers-reduced-motion: no-preference) {159 @supports (animation-timeline: view()) {160 .scroll-fade {161 animation: fadeIn linear both;162 animation-timeline: view();163 animation-range: entry 0% entry 50%;164 }165 }166}167168/* JS fallback for older browsers */169.scroll-fade {170 opacity: 0;171 transform: translateY(20px);172 transition: opacity 400ms ease, transform 400ms ease;173}174175.scroll-fade.is-visible {176 opacity: 1;177 transform: none;178}179```180181(Include a small IntersectionObserver script in the output when using the fallback.)182183**Example 5: Attention Pulse (for notifications)**184185```css186@keyframes pulse {187 0%, 100% { transform: scale(1); }188 50% { transform: scale(1.08); }189}190191@media (prefers-reduced-motion: no-preference) {192 .attention-pulse {193 animation: pulse 2s cubic-bezier(0.4, 0, 0.2, 1) infinite;194 }195}196```197198## Edge Cases & Error Handling199200- **User has `prefers-reduced-motion: reduce`**: Always provide a no-animation path. For loading spinners that are essential, use a static indicator + ARIA live region instead of infinite animation.201- **Animation causes layout thrashing**: Only animate `transform` and `opacity`. If the user requests width/height animation, provide a `transform: scale` alternative and explain the performance difference.202- **Too many staggered items**: Cap stagger at ~10-12 items or use a CSS variable that the agent can adjust. Suggest IntersectionObserver + class toggle for very long lists.203- **Browser support**: For advanced features (scroll timelines, `@property`), always provide a progressive enhancement comment and a basic fallback.204- **Accessibility**: Never use animation to convey critical information without a static alternative. For attention animations, also use color or icon changes.205- **Performance on low-end devices**: Recommend testing with DevTools throttling. Suggest `will-change: transform` only on the animating element and remove it after the animation ends.206- **Conflict with Tailwind**: Show how to use arbitrary values (`animate-[spin_800ms_linear_infinite]`) and how to extend Tailwind config with custom keyframes.207208## Verification2092101. Paste the generated CSS + HTML into a test file and open in Chrome, Firefox, and Safari.2112. **Reduced motion test**: Set `prefers-reduced-motion: reduce` in DevTools (Rendering panel) — confirm animations are disabled or greatly simplified.2123. **Performance check**: Use Chrome DevTools Performance tab while triggering the animation. Confirm no layout thrashing (purple bars minimal) and GPU compositing (green bars for the layer).2134. **Accessibility**: Use keyboard to trigger hover/focus states. Verify focus is never lost due to animation. Run axe or Lighthouse — motion-related issues should be absent.2145. **Stagger verification**: Inspect computed `animation-delay` values on list items; they should increment correctly.2156. Success: Animations are smooth (60fps), respect user preferences, use only safe properties, and enhance (not distract from) the UI.216217## References218219- [MDN CSS Animations](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations)220- [MDN CSS Transitions](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions)221- [prefers-reduced-motion on MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)222- [Scroll-driven Animations Spec](https://drafts.csswg.org/css-animations-2/#scroll-driven-animations)223- [CSS Triggers](https://csstriggers.com/) — which properties cause layout/paint224- [Cubic-bezier.com](https://cubic-bezier.com) for custom easing visualization