# Compositor-friendly

**MUST** · **ID:** `animations-compositor-friendly` · **Category:** animations
**Source:** [Vercel](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/animations-compositor-friendly

> MUST: Animate compositor-friendly props (`transform`, `opacity`) — they run on the GPU without layout or paint; avoid layout/repaint props (`top/left/width/height`, `margin`). 60fps leaves <16ms per frame

Prioritize GPU-accelerated properties like transform and opacity

transform and opacity are the only two properties the browser can change without redoing style, layout or paint — the compositor already holds the layer and just re-places or re-blends it, on its own thread. That is why they keep moving at 60fps even while the main thread is blocked, and why width, height, top or left stutter the moment it is not. This is one of the most independently rediscovered rules in the field, and this entry is where the corpus records it once: ibelick's baseline-ui states it as "MUST animate only compositor props (`transform`, `opacity`)", and Tailwind performance guidance as "animate only transform and opacity" — three sources, one rule, so read the convergence as confirmation rather than as three things to learn. Read it as the DEFAULT, not an absolute: the carve-outs are priced elsewhere and are part of the rule. Paint animations are acceptable on small, isolated surfaces (a button label, an icon — animations-ibelick-minimize-paint), one-shot effects are cheaper than continuous motion, and a layout-like change can be faked with a measured transform (FLIP — animations-flip-technique). And note the false floor: in Motion, `animate={{ x: 100 }}` looks compliant but is interpolated on the main thread — see performance-motion-shorthand-not-gpu.

## Bad — do not do this

`animations-compositor-friendly-bad`

```tsx
import { useState } from 'react';

export function CompositorFriendlyBad() {
  const [big, setBig] = useState(false);

  return (
    <div className="space-y-4">
      <button
        onClick={() => setBig((v) => !v)}
        className="px-4 py-2 bg-primary text-primary-foreground rounded-lg transition-colors duration-150 ease-out hover:bg-primary/90"
      >
        Toggle size
      </button>
      <div className="grid place-items-center h-40">
        <div
          className="rounded-xl bg-primary"
          style={{
            width: big ? 128 : 64,
            height: big ? 128 : 64,
            transition: 'width 400ms ease, height 400ms ease',
          }}
        />
      </div>
      <p className="text-xs text-destructive">
        Growing with <code>width</code>/<code>height</code> runs a layout pass on the main thread every frame
      </p>
    </div>
  );
}
```

## Good — do this

`animations-compositor-friendly-good`

```tsx
import { useState } from 'react';

export function CompositorFriendlyGood() {
  const [big, setBig] = useState(false);

  return (
    <div className="space-y-4">
      <button
        onClick={() => setBig((v) => !v)}
        className="px-4 py-2 bg-primary text-primary-foreground rounded-lg transition-colors duration-150 ease-out hover:bg-primary/90"
      >
        Toggle size
      </button>
      <div className="grid place-items-center h-40">
        <div
          className="w-16 h-16 rounded-xl bg-primary"
          style={{
            transform: big ? 'scale(2)' : 'scale(1)',
            transition: 'transform 400ms cubic-bezier(0.16, 1, 0.3, 1)',
          }}
        />
      </div>
      <p className="text-xs text-success">
        The same growth via <code>transform: scale</code> runs on the compositor — smooth 60fps, no reflow
      </p>
    </div>
  );
}
```

## References

- [High Performance Animations](https://web.dev/articles/animations-guide)
- [Compositor-only Properties](https://web.dev/articles/stick-to-compositor-only-properties-and-manage-layer-count)
- [ibelick — baseline-ui SKILL.md](https://github.com/ibelick/ui-skills/blob/main/skills/baseline-ui/SKILL.md)
- [CSS Triggers](https://csstriggers.com/)
