# Performance Optimization: Why You Should Only Animate Transform and Opacity

> Boost web performance by animating only transform and opacity. Keep rendering on the GPU for smooth 60 FPS animations and avoid costly layout and paint processes.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: performance
- Published: 2026-07-31

---

**Animating exclusively `transform` and `opacity` keeps rendering on the GPU, bypassing expensive layout and paint calculations to maintain 60 FPS even under heavy browser load.**

The `emilkowalski/skills` repository enforces strict performance guidelines for UI animations, mandating that developers restrict animations to `transform` and `opacity` to ensure GPU acceleration and prevent frame drops. This performance optimization strategy eliminates layout thrashing by avoiding properties that trigger the browser's full rendering pipeline.

## The Browser Rendering Pipeline and GPU Acceleration

Animations that touch layout-affecting properties—such as `width`, `height`, `margin`, `top`, or `left`—force the browser to execute a complete three-step pipeline: *layout*, then *paint*, and finally *composite*. This process is computationally expensive and frequently causes frame drops on busy pages.

In contrast, `transform` and `opacity` are **GPU-only properties**. The browser can skip the layout and paint phases entirely, compositing changes directly on the GPU. According to [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), this allows animations to "skip layout/paint and run on the GPU"【112】, delivering consistent 60 fps performance even under load.

## Critical Rules from the Skills Repository

### Avoid Layout-Triggering Properties

Never animate properties that affect document flow. As documented in [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md), properties like `width`, `height`, `margin`, `padding`, `top`, and `left` trigger the full rendering pipeline【73】, forcing the browser to recalculate geometry and repaint pixels on every frame.

### Framer Motion Anti-Patterns

Do not use Framer Motion's shorthand properties (`x`, `y`, `scale`). According to [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md), these shorthands are implemented on the main thread via `requestAnimationFrame`, which defeats GPU acceleration【75】. Instead, use the full `transform` string:

```tsx
<motion.div animate={{ transform: "translateX(100px)" }} />

```

### The CSS Variable Trap

Never drive child transforms via a CSS variable on the parent. Updating a CSS variable causes a style recalculation for every descendant, negating the GPU benefit【76】. As noted in [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md), you should apply the `transform` directly to the target element rather than updating a variable that multiple children reference.

## Practical Implementation Examples

### Press Feedback with CSS

For subtle button interactions, use `transform` and `opacity` with short transitions:

```css
.button {
  transition: transform 160ms ease-out, opacity 160ms ease-out;
}

.button:active {
  transform: scale(0.97);
  opacity: 0.95;
}

```

This pattern, referenced in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md)【31‑35】, provides tactile feedback without triggering layout calculations.

### Popover Animations with Transform Origin

Anchor popovers properly using `transform-origin` while keeping animations GPU-friendly:

```css
.popover {
  transform-origin: var(--transform-origin);
  opacity: 0;
  transform: scale(0.95);
  transition: transform 200ms ease-out, opacity 200ms ease-out;
}

.popover[data-open] {
  opacity: 1;
  transform: scale(1);
}

```

The repository defines `--transform-origin` for trigger-anchored components, noting that modals are the only exception where `center` is acceptable【112‑113】.

### React Patterns with Framer Motion

When using Framer Motion in React, always specify the full transform string:

```tsx
import { motion } from "framer-motion";

export const SlideIn = () => (
  <motion.div
    initial={{ opacity: 0, transform: "translateY(20px)" }}
    animate={{ opacity: 1, transform: "translateY(0)" }}
    transition={{ duration: 0.2, ease: "easeOut" }}
  />
);

```

Avoid the shorthand `x: 20` or `scale: 0.95` syntax, as these bypass hardware acceleration.

### Direct DOM Manipulation

When updating positions via JavaScript, set the property directly on the element:

```javascript
// Bad: Updates CSS variable, causing style recalc for all children
container.style.setProperty('--x', `${dx}px`);

// Good: GPU-only transform update
element.style.transform = `translateX(${dx}px)`;

```

Direct `transform` updates avoid costly style recalculations for descendant elements【76】.

## Summary

- **Animate only `transform` and `opacity`** to keep rendering on the GPU and skip layout/paint phases.
- **Avoid layout properties** like `width`, `height`, `margin`, and positional offsets that trigger expensive pipeline calculations.
- **Use full transform strings** in Framer Motion instead of shorthands (`x`, `y`, `scale`) to ensure hardware acceleration.
- **Apply transforms directly** to animated elements rather than driving them through CSS variables to prevent descendant style recalculations.
- **Reference [`skills/prototype/PICKER.md`](https://github.com/emilkowalski/skills/blob/main/skills/prototype/PICKER.md)** for the rare intentional exception (width transitions) and its specific safety constraints.

## Frequently Asked Questions

### Why does animating width or height hurt performance?

Animating `width` or `height` forces the browser to run a layout pass on every frame, recalculating the geometry of the element and its siblings, followed by a paint pass to fill the new pixels. This triple-step process (layout → paint → composite) consumes significantly more resources than GPU-only properties, often resulting in frame rates below 60 FPS on complex pages.

### Why are Framer Motion's x/y/scale shorthands problematic?

Framer Motion implements shorthand properties like `x`, `y`, and `scale` using `requestAnimationFrame` on the main thread rather than the compositor thread. According to [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md), this implementation detail means these animations cannot leverage GPU acceleration, causing them to hitch under load unlike native CSS transforms.

### What's wrong with using CSS variables for coordinated animations?

Updating a CSS custom property (variable) to drive child element positions triggers a style recalculation for every descendant selector that references that variable. As documented in [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md), this global invalidation negates the performance benefits of GPU acceleration, whereas setting `transform` directly on individual elements limits scope and maintains 60 FPS.

### Are there exceptions to the transform and opacity rule?

Yes, but they require explicit justification. The repository references [`skills/prototype/PICKER.md`](https://github.com/emilkowalski/skills/blob/main/skills/prototype/PICKER.md) as an example where width transitions are intentionally permitted with detailed explanations of why the performance tradeoff is acceptable in that specific context. For standard UI components, the rule remains absolute: only `transform` and `opacity`.