# How to Build Animations from Scratch Using the 'animate' Skill

> Learn to build animations from scratch with the emilkowalski/skills animate skill. Transform motion requests into production-ready code using its 7-step decision sequence for efficient animation development.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: how-to-guide
- Published: 2026-08-08

---

**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`](https://github.com/emilkowalski/skills/blob/main/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`](https://github.com/emilkowalski/skills/blob/main/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:

```css
.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:

```css
.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:

```css
.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:

```css
.drawer {
  transform: translateY(0);
  transition: transform 500ms var(--ease-drawer);
}
.drawer[data-closed] {
  transform: translateY(100%);
}

```

- **Purpose:** spatial consistency
- **Note:** Uses layout-independent `translateY` for compositor-only animation

### Hold-to-Confirm Destructive Actions

Use `clip-path` for deliberate, time-based confirmation:

```css
.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:

```javascript
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`](https://github.com/emilkowalski/skills/blob/main/SKILL.md):

```css
@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`](https://github.com/emilkowalski/skills/blob/main/skills/animate/SKILL.md)
- **Tool selection hierarchy**: Prefer CSS transitions, then `@starting-style`, then CSS animations, then WAAPI, then Motion
- **Property restriction**: Use only `transform` and `opacity` for 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-motion` and hover/pointer media queries
- **Reference implementations**: Copy patterns from [`skills/animate/RECIPES.md`](https://github.com/emilkowalski/skills/blob/main/skills/animate/RECIPES.md) for 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`](https://github.com/emilkowalski/skills/blob/main/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.