# 7 Forbidden Animation Patterns in Taste Skill: A Complete Guide to Motion Anti-Patterns

> Discover the 7 forbidden animation patterns in Taste Skill including infinite loops and raw scroll listeners. Master UI motion for performance and accessibility.

- Repository: [Leon Lin/taste-skill](https://github.com/Leonxlnx/taste-skill)
- Tags: how-to-guide
- Published: 2026-05-30

---

**Taste Skill explicitly bans seven specific animation anti-patterns—including infinite-loop micro-animations, raw scroll listeners, and unmotivated "cool-look" effects—to enforce performant, accessible, and purposeful UI motion.**

Taste Skill is a strict front-end motion specification defined in [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md) within the Leonxlnx/taste-skill repository. These **forbidden animation patterns** live in Section 5.D and are designed to prevent GPU waste, layout thrashing, and accessibility violations. Violating any of these rules triggers a pre-flight build failure.

## The Seven Forbidden Animation Patterns

### Infinite-Loop Micro-Animations Everywhere

Taste Skill prohibits endless pulsing, rotating, or floating elements that never stop. According to Section 1 of [`SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/SKILL.md) (line 39), these create visual noise, waste GPU cycles, and break the "motion must be motivated" rule. Any perpetual motion without user interaction or state change is considered a violation.

### Raw Window Scroll Event Listeners

Using `window.addEventListener("scroll", …)` is banned at line 511 of [`SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/SKILL.md). This pattern runs on every scroll frame, causes jank, and bypasses React batching. The specification mandates using **Motion’s `useScroll()`**, GSAP ScrollTrigger, `IntersectionObserver`, or native CSS scroll-driven animations instead.

### Scroll-Progress Calculations in React State

Storing `window.scrollY` in React state (lines 512-513) forces full component re-renders on every pixel scroll, crushing FPS on mobile devices. The skill requires leveraging **Motion values** (`useMotionValue`, `useTransform`) to handle scroll progress without triggering React’s render cycle.

### requestAnimationFrame State Updates

Writing to React state inside `requestAnimationFrame` loops (lines 513-514) creates the same re-render bottleneck as scroll listeners. Taste Skill enforces using **Motion values** for imperative animation updates, keeping calculations off the main thread and out of React’s state management.

### Layout Transition Misuse on Static Content

Wrapping purely static markup in `layout` or `layoutId` props "just to look fancy" is forbidden (lines 514-515). This adds unnecessary measurement work for the browser’s layout engine without any real UI benefit or state transition purpose.

### Multiple Horizontal Marquees

The **"MARQUEE MAX-ONE-PER-PAGE"** rule (line 361) limits kinetic marquee strips to a single instance per page. Multiple marquees are considered lazy filler content that distracts from meaningful hierarchy.

### Unmotivated "Cool-Look" Animations

Any animation that cannot be explained in one sentence as serving **hierarchy, storytelling, feedback, or state transition** is banned (lines 360-362). Taste Skill insists every motion has a clear communication purpose; decorative motion without motivation violates the core "motion must be motivated" principle.

## Performance-First Alternatives Allowed in Taste Skill

### Motion whileInView for Scroll Reveals

Replace scroll listeners with Motion’s declarative viewport detection:

```tsx
"use client";
import { motion, useReducedMotion } from "motion/react";

export function RevealStagger({ items }: { items: string[] }) {
  const reduce = useReducedMotion();
  return (
    <ul className="grid gap-6">
      {items.map((item, i) => (
        <motion.li
          key={item}
          initial={reduce ? false : { opacity: 0, y: 24 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true, amount: 0.3 }}
          transition={{
            duration: 0.6,
            delay: i * 0.06,
            ease: [0.16, 1, 0.3, 1],
          }}
        >
          {item}
        </motion.li>
      ))}
    </ul>
  );
}

```

This approach respects `prefers-reduced-motion` and never touches `window.scroll`.

### GSAP ScrollTrigger for Complex Effects

For imperative scroll orchestration, GSAP ScrollTrigger is the only accepted library (Section 5.A):

```tsx
"use client";
import { useRef, useEffect } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(ScrollTrigger);

export function StickyStack({ cards }: { cards: React.ReactNode[] }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      const sections = gsap.utils.toArray<HTMLElement>(".stack-card");
      sections.forEach((el, i) => {
        if (i === sections.length - 1) return;
        ScrollTrigger.create({
          trigger: el,
          start: "top top",
          pin: true,
          pinSpacing: false,
        });
        gsap.to(el, {
          opacity: 0.55,
          scale: 0.92,
          scrollTrigger: {
            trigger: sections[i + 1],
            start: "top bottom",
            end: "top top",
            scrub: true,
          },
        });
      });
    }, ref);
    return () => ctx.revert();
  }, []);

  return (
    <div ref={ref}>
      {cards.map((c, i) => (
        <div key={i} className="stack-card min-h-[100dvh] flex items-center justify-center">
          {c}
        </div>
      ))}
    </div>
  );
}

```

This implementation batches DOM reads/writes and cleans up on unmount.

### Single-Use Marquee Implementation

When a marquee is justified by the design narrative, implement exactly one per page:

```tsx
"use client";
import { motion } from "motion/react";

export function SingleMarquee({ items }: { items: string[] }) {
  return (
    <motion.div
      className="whitespace-nowrap overflow-hidden"
      animate={{ x: "-100%" }}
      transition={{ repeat: Infinity, ease: "linear", duration: 12 }}
    >
      {items.map((t, i) => (
        <span key={i} className="inline-block mr-8">{t}</span>
      ))}
    </motion.div>
  );
}

```

This satisfies the Section 5.D constraint that only one horizontal marquee may exist per view.

## Summary

- **Section 5.D** of [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md) defines seven specific animation anti-patterns that cause pre-flight failures.
- **Infinite loops**, raw scroll listeners, and state-based scroll calculations are banned to prevent GPU and main-thread saturation.
- **Layout transitions** must serve actual UI state changes, not static decoration.
- **Marquees** are capped at one per page to avoid kinetic filler.
- **Motion values** and GSAP ScrollTrigger are the approved alternatives to React state for scroll-based animations.
- All animations must be **motivated** by hierarchy, storytelling, feedback, or state transition.

## Frequently Asked Questions

### What happens if I use a forbidden animation pattern in Taste Skill?

The build system triggers a pre-flight failure. According to the specification in [`SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/SKILL.md), these rules are enforced by higher-level motion guards that scan for banned patterns like `window.addEventListener("scroll")` or unmotivated layout transitions before deployment.

### Can I use CSS scroll-driven animations with Taste Skill?

Yes. Native CSS scroll-driven animations are explicitly allowed as alternatives to JavaScript scroll listeners. They run on the compositor thread, avoid React re-renders, and satisfy the performance requirements outlined in Section 5.D.

### Why does Taste Skill limit marquees to one per page?

Multiple horizontal marquees are classified as lazy filler content that creates visual noise without communication purpose. The **"MARQUEE MAX-ONE-PER-PAGE"** rule (line 361) enforces editorial discipline, ensuring kinetic motion is used sparingly and with intent.

### How do I handle scroll-based animations without window.addEventListener?

Use **Motion’s `useScroll()`** hook for React-based scroll tracking, **GSAP ScrollTrigger** for complex pinning and scrubbing, or **CSS scroll-driven animations** for native performance. These approaches read scroll position outside React’s render cycle, preventing the FPS drops caused by state updates on every scroll frame.