Animation Decision Framework in the emilkowalski/skills Repository: A Complete Guide

The Animation Decision Framework is a four-step decision tree in skills/emil-design-eng/SKILL.md that guides developers to create purposeful, performant UI animations by answering: should this animate, what's the purpose, what easing, and how fast.

This framework serves as the authoritative standard for animation decisions across the emilkowalski/skills repository. Rather than arbitrary aesthetic choices, it enforces frequency-based restrictions, purpose-driven design, and performance-conscious timing. Any developer or AI agent contributing to this codebase must consult the framework before writing animation code.

The Four Steps of the Animation Decision Framework

The framework consists of four ordered questions, each with concrete criteria and default recommendations. According to the source code in skills/emil-design-eng/SKILL.md (lines 66–134), these steps form a mandatory checklist.

Step 1: Should This Animate at All?

Frequency determines permission. The framework categorizes UI elements by how often users encounter them:

Frequency Examples Decision
100+ times/day Keyboard shortcuts, command-palette toggles Never animate
Tens of times/day Hover effects, list navigation Drastically reduce or remove
Occasional Modals, drawers, toasts Standard animation allowed
Rare/first-time Onboarding, celebrations Add delight if desired

This rule appears at line 66–76 in SKILL.md and acts as the primary gate. High-frequency animations create fatigue and degrade perceived performance regardless of how well-executed they are.

Step 2: What Is the Purpose?

Every animation must justify its existence. Valid animation purposes recognized by the framework include:

  • Spatial consistency — maintaining object permanence during layout shifts
  • State indication — showing mode changes or selections
  • Explanation — clarifying cause-and-effect relationships
  • Feedback — confirming user actions
  • Preventing jarring changes — smoothing transitions that would otherwise disorient

If the sole justification is aesthetic appeal ("it looks cool") and the animation is frequent, the framework mandates don't animate. This guard appears at lines 81–94 in SKILL.md.

Step 3: What Easing Should It Use?

The framework prescribes easing curves based on animation role, as documented at lines 95–107:

Animation Role Easing Implementation
Enter/exit ease-out cubic-bezier(0.23, 1, 0.32, 1)
Moving/morphing on-screen ease-in-out cubic-bezier(0.77, 0, 0.175, 1)
Hover/color change ease CSS default
Constant motion (marquee, progress) linear CSS default
Default fallback ease-out Same as enter/exit

Critical prohibition: Never use ease-in for UI interactions. The framework explicitly bans this curve because it creates sluggish, unresponsive-feeling motion.

Step 4: How Fast Should It Be?

Duration caps ensure perceived responsiveness. From lines 124–134 in SKILL.md, all UI animations must stay ≤ 300ms with these standard ranges:

Element Duration
Button press feedback 100–160 ms
Tooltips / small popovers 125–200 ms
Dropdowns, selects 150–250 ms
Modals, drawers 200–500 ms
Marketing / explanatory Longer as needed

Faster animations universally improve perceived performance. The 300ms ceiling is non-negotiable for functional UI elements.

Applying the Framework in Code

The following TypeScript/React implementations demonstrate how to codify each framework step. These patterns align with the repository's use of framer-motion and CSS custom properties.

Frequency Guard Function

/**
 * Returns `true` if the element should be animated based on frequency.
 * Frequency categories: 'high', 'medium', 'occasional', 'rare'.
 * 
 * Reference: SKILL.md lines 66-76
 */
export function shouldAnimate(
  frequency: 'high' | 'medium' | 'occasional' | 'rare'
): boolean {
  const decisions = {
    high: false,        // 100+ times/day → no animation
    medium: false,      // tens/day → reduce or remove
    occasional: true,   // standard animation
    rare: true,         // add delight if desired
  };
  return decisions[frequency];
}

Purpose Validation

type AnimationPurpose =
  | 'spatial-consistency'
  | 'state-indication'
  | 'explanation'
  | 'feedback'
  | 'prevent-jarring';

export function assertPurpose(purpose: AnimationPurpose): void {
  if (!purpose) {
    throw new Error(
      'Animation must have a clear purpose per Animation Decision Framework'
    );
  }
}

Easing Selector with CSS Variables

/* styles.css — centralized curve definitions per SKILL.md lines 95-107 */
:root {
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
}
export function getEasing(
  entering: boolean,
  moving: boolean,
  hover: boolean,
  constant: boolean
): string {
  if (entering) return 'var(--ease-out)';
  if (moving) return 'var(--ease-in-out)';
  if (hover) return 'ease';
  if (constant) return 'linear';
  return 'var(--ease-out)'; // default per framework
}

Duration Constants

// SKILL.md lines 124-134 — enforce ≤300ms for functional UI
export const DURATION = {
  button: 120,    // ms — button press feedback
  tooltip: 150,   // small popovers
  dropdown: 200,  // selects, menus
  modal: 300,     // cap for functional elements
} as const;

export function getDuration(
  element: keyof typeof DURATION
): string {
  return `${DURATION[element]}ms`;
}

Complete Integration Example

import { shouldAnimate, assertPurpose, getEasing, getDuration } from './animation-utils';

export function AnimatedButton({ label }: { label: string }) {
  // Step 1️⃣: Frequency check
  const animate = shouldAnimate('occasional');

  // Step 2️⃣: Purpose declaration
  assertPurpose('feedback');

  // Early return for prohibited animations
  if (!animate) return <button>{label}</button>;

  // Steps 3️⃣ & 4️⃣: Easing and duration selection
  const easing = getEasing(true, false, false, false); // entering=true
  const duration = getDuration('button');

  return (
    <button
      style={{
        transition: `transform ${duration} ${easing}`,
      }}
      onMouseDown={(e) => (e.currentTarget.style.transform = 'scale(0.97)')}
      onMouseUp={(e) => (e.currentTarget.style.transform = 'scale(1)')}
    >
      {label}
    </button>
  );
}

The Animation Decision Framework propagates through multiple skill files in the repository:

File Function Framework Integration
skills/emil-design-eng/SKILL.md Primary definition Lines 66–134 contain the complete four-step framework
skills/animate/SKILL.md Build implementation Applies framework decisions to concrete animation construction
skills/review-animations/SKILL.md Automated validation Uses framework as checklist in code reviews
skills/improve-animations/SKILL.md Audit and prioritize Framework decisions determine fix priority

This distributed architecture ensures the framework enforces itself — no single file contains the full enforcement logic, yet together they create unavoidable compliance checks.

Summary

  • The Animation Decision Framework lives in skills/emil-design-eng/SKILL.md and mandates four ordered checks before any animation code
  • Frequency is the primary filter: elements shown 100+ times daily must not animate
  • Purpose validation eliminates purely decorative motion in high-frequency contexts
  • Easing selection follows strict role-based rules with ease-in explicitly prohibited
  • Duration ceiling of 300ms for functional UI ensures responsive feel
  • The framework propagates through related skills (animate, review-animations, improve-animations) to enforce compliance at build, review, and audit stages

Frequently Asked Questions

Where is the Animation Decision Framework documented in the repository?

The primary documentation resides in skills/emil-design-eng/SKILL.md at lines 66–134. This file defines all four decision steps with specific criteria, easing curves, and duration tables. Related skills in skills/animate/, skills/review-animations/, and skills/improve-animations/ extend and apply the framework to specific workflows.

Why does the framework prohibit animations for high-frequency elements?

Elements shown 100+ times per day create animation fatigue and degrade perceived performance regardless of execution quality. The framework prioritizes user experience over aesthetic polish, recognizing that repeated motion becomes distracting and slows task completion. This frequency gate is the first and strictest filter in the decision tree.

What happens if I use ease-in for a UI interaction?

The framework explicitly bans ease-in at lines 95–107 of SKILL.md because it creates sluggish, unresponsive-feeling motion. Slow starts make interfaces feel delayed and unpolished. The recommended alternatives — ease-out for enter/exit, ease-in-out for on-screen morphing — provide natural motion that respects user attention and maintains perceived responsiveness.

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 →