Hierarchy of Tools Recommended by the `animate` Skill: From CSS Transitions to Motion

The animate skill recommends five tools in escalating order of power: CSS transitions (simplest), CSS @starting-style for mount animations, CSS animations for main-thread resilience, WAAPI for programmatic control, and Motion (motion.dev) for springs, gestures, and layout animations.

The animate skill in emilkowalski/skills provides a structured decision-making framework for adding motion to web interfaces. When you reach step 3—"Pick the tool — cheapest that works"—it presents a clear hierarchy designed to minimize performance overhead and avoid unnecessary dependencies. This article breaks down that hierarchy exactly as documented in [skills/animate/SKILL.md](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md).

The Five-Level Tool Hierarchy

The hierarchy progresses from zero-JavaScript solutions to full-featured libraries. Each level assumes the previous options failed to meet your specific need.

Level 1: CSS Transition (Hover, Press, State Toggles)

Use when: You need to animate hover states, active presses, or any visual change controlled by a class or attribute change.

This is the cheapest option—no JavaScript, no parsing overhead, hardware-accelerated by default. The browser optimizes these automatically on the compositor thread.

<button class="btn">Hover me</button>

<style>
.btn {
  background: #0066ff;
  transition: background 150ms ease;
}
.btn:hover {
  background: #0044aa;
}
</style>

Key insight from the source: CSS transitions sit at the foundation of the animate skill hierarchy because they trigger on computed value changes without any script execution.

Level 2: CSS @starting-style (Entry Animations on Mount)

Use when: You need an entrance animation that fires when an element first appears, with no JavaScript state management.

The @starting-style at-rule lets you define the pre-mount state for an element, enabling pure-CSS mount animations without useEffect or requestAnimationFrame hacks.

<div class="modal">…</div>

<style>
@starting-style {
  .modal {
    opacity: 0;
    transform: scale(0.95);
  }
}
.modal {
  animation: fadeIn 200ms ease-out forwards;
}
@keyframes fadeIn {
  to { opacity: 1; transform: scale(1); }
}
</style>

This pattern eliminates the flash-of-unstyled-content problem that previously forced developers toward JavaScript solutions.

Level 3: CSS Animation (Resilient During Page Load)

Use when: You need predetermined motion that must stay smooth even when the main thread is blocked by JavaScript execution.

Unlike JavaScript-driven approaches, CSS animations run on a separate thread (the compositor). They continue smoothly while the page executes heavy scripts or parses large modules.

.spinner {
  width: 24px;
  height: 24px;
  animation: spin 1s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

Performance note: This is the last purely declarative option in the hierarchy. Everything beyond this point requires JavaScript evaluation.

Level 4: WAAPI (element.animate())

Use when: You need programmatic control—dynamic durations, runtime-generated keyframes, or playback manipulation—while maintaining CSS-level performance.

The Web Animations API (WAAPI) provides imperative JavaScript control without shipping animation properties across the bundle. It uses the same browser engine as CSS animations, so performance characteristics match.

const el = document.querySelector('.panel');
el.animate(
  [{ opacity: 0, transform: 'translateY(-10px)' },
   { opacity: 1, transform: 'translateY(0)' }],
  { duration: 180, easing: 'ease-out', fill: 'forwards' }
);

Source reference: In skills/animate/SKILL.md lines 63-70, WAAPI is positioned as the bridge between pure CSS and full libraries—offering JavaScript flexibility with zero dependency cost.

Level 5: Motion (motion.dev)

Use when: You need physics-based springs, layout animations, exit animations, or gesture-driven values—features impossible in lower levels.

Motion (formerly Framer Motion) provides declarative React APIs for complex motion patterns. It handles layout projection, shared element transitions, and pointer-driven springs that would require thousands of lines of WAAPI code.

import { motion } from 'motion';

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ type: 'spring', stiffness: 300, damping: 30 }}
/>

This is the only recommended dependency in the hierarchy. The animate skill explicitly reserves it for cases where native APIs cannot satisfy the requirement.

How to Apply the Hierarchy in Practice

The animate skill encodes a specific decision flow. Before selecting any tool, verify that cheaper alternatives cannot solve your problem:

  1. Start with CSS transitions for all state-based visual feedback
  2. Escalate to @starting-style only when mounting needs animation
  3. Use CSS animations when smoothness during load is critical
  4. Reach for WAAPI when you need dynamic, script-controlled motion
  5. Add Motion exclusively for springs, gestures, layout shifts, or exit animations

This progression prevents bundle bloat and maximizes runtime performance. The full reasoning appears in [skills/animate/SKILL.md](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md), with concrete implementation patterns in [skills/animate/RECIPES.md](https://github.com/emilkowalski/skills/blob/main/skills/animate/RECIPES.md).

Summary

  • CSS transitions handle the majority of UI motion needs with zero overhead
  • CSS @starting-style enables mount animations without JavaScript state
  • CSS animations guarantee smooth playback during main thread congestion
  • WAAPI provides programmatic control matching CSS performance characteristics
  • Motion resolves advanced requirements (springs, gestures, layout) that native APIs cannot address

Frequently Asked Questions

What order does the animate skill recommend for selecting animation tools?

The animate skill orders tools by cost and complexity: CSS transitions first, then CSS @starting-style, CSS animations, WAAPI, and finally Motion as the heavyweight solution. This sequence ensures you reach for dependencies only when native APIs prove insufficient.

When should I use WAAPI instead of CSS animations?

Choose WAAPI when you need runtime-generated keyframes, dynamic duration changes, or playback control like pause() and reverse(). CSS animations require predefined keyframes in stylesheets—WAAPI constructs them in JavaScript while preserving the same thread-isolated performance.

Why does CSS @starting-style exist as a separate step from transitions?

@starting-style solves a specific problem: styling the initial state of an element before it enters the DOM. Transitions only animate changes to existing elements. Without @starting-style, mount animations previously required JavaScript to force a reflow or state management to delay visibility—now handled declaratively in CSS.

Is Motion always necessary for React animations?

No—according to the animate skill hierarchy, Motion is specifically reserved for springs, layout animations, exit animations, and gesture-driven values. Simple fades, slides, and state changes should use CSS transitions or WAAPI to avoid the dependency cost and runtime overhead of a full motion library.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →