# How to Animate `transform` and `opacity` Only for Maximum Performance

> Animate transform and opacity only for maximum browser performance. Learn why these properties skip layout and paint for smoother, faster animations.

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

---

**Animating `transform` and `opacity` exclusively is the definitive browser performance optimization because these properties skip layout and paint, running entirely on the GPU at 60+ fps.**

Modern browsers render every frame through three sequential stages—**layout → paint → composite**. Only the final composite stage can be fully offloaded to the GPU. When you animate properties that trigger layout recalculation, the browser must recompute geometry for the entire document, repaint affected regions, and only then composite. This cascade destroys frame rates. The `emilkowalski/skills` repository encodes this knowledge into strict standards: **animate `transform` and `opacity` only**, never `width`, `height`, `margin`, `padding`, `top`, or `left`.

## Why Layout-Triggering Properties Kill Performance

Properties that affect element dimensions or position force **synchronous layout**—the most expensive operation in the rendering pipeline.

| Property | Rendering Impact | Typical Cost | GPU/CPU |
|----------|---------------|------------|---------|
| `width`, `height`, `top`, `left`, `margin`, `padding` | Triggers **layout → paint → composite** | High (reflows entire page) | CPU-bound |
| `transform`, `opacity` | Skips layout & paint, straight to **composite** | Low (matrix/alpha update only) | GPU-bound |

According to [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md) (lines 73-75): *"Animate `transform` and `opacity` only. `width`/`height`/`margin`/`padding`/`top`/`left` trigger all three rendering steps."*

The CPU cost of layout is particularly brutal on low-end devices and during complex animations. GPU-composited transforms, by contrast, execute as simple matrix multiplications with no DOM geometry recalculation.

## The Three Rendering Stages Explained

### Layout (Reflow)

The browser calculates where every element sits and how much space it occupies. Changing `width`, `height`, or `left` forces this calculation—often for the entire document, not just the animated element.

### Paint

The browser fills in pixels: text, colors, images, borders. Paint is expensive on high-DPI screens and when overlapping elements create complex clipping regions.

### Composite

The GPU assembles painted layers into the final screen image. This is the only stage that runs asynchronously and scales with hardware acceleration.

**`transform` and `opacity` bypass stages 1 and 2 entirely.** The browser promotes the element to its own compositor layer, then the GPU handles all subsequent changes.

## Repository Standards and Enforcement

The `emilkowalski/skills` repository treats this rule as non-negotiable across multiple documentation files:

- **[`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md)** (lines 112-113): *"Only animate `transform` and `opacity` — they skip layout/paint and run on the GPU."*

- **[`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md)** (lines 73-75): Explicitly lists forbidden properties and their rendering impact.

- **[`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md)** (lines 35-37): *"GPU-only properties. Animate `transform` and `opacity` only. Animating layout properties (or Framer-Motion shorthands) is a performance finding."*

These files are referenced during code review to flag violations before they reach production.

## Common Performance Pitfalls and Fixes

### Pitfall: `transition: all`

The `all` keyword captures every animatable property, including layout-triggering ones that slip in unexpectedly.

**Incorrect:**

```css
.card {
  transition: all 200ms ease-out;
}

```

**Correct:**

```css
.card {
  transition: transform 200ms ease-out,
              opacity 200ms ease-out;
}

```

Source: [`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md), lines 86-87.

### Pitfall: Framer Motion Shorthands

Framer Motion's `x`, `y`, and `scale` props run on the main thread via `requestAnimationFrame`, dropping frames under load.

**Incorrect:**

```tsx
<motion.div animate={{ x: 100, scale: 1.2 }} />

```

**Correct:**

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

```

Source: [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md), lines 75-76.

### Pitfall: CSS Variable-Driven Child Transforms

Setting a transform-related CSS variable on a parent causes style recalculation for all children.

**Incorrect:**

```css
.parent {
  --y-offset: 0px;
}
.child {
  transform: translateY(var(--y-offset));
}

```

**Correct:**

```js
element.style.transform = `translateY(${offset}px)`;

```

Source: [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), lines 115-116.

### Pitfall: Scaling from `scale(0)`

Elements scaled to zero lose their visual anchor, appearing to pop in from nowhere rather than emerging smoothly.

**Correct:**

```css
.popover {
  transform: scale(0.95);
  opacity: 0;
  transition: transform 200ms ease-out,
              opacity 200ms ease-out;
}
.popover[data-open] {
  transform: scale(1);
  opacity: 1;
}

```

Source: [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), lines 59-60.

## Practical Implementation Examples

### Button Press Feedback

Subtle scale reduction provides tactile feedback without layout impact:

```css
button:active {
  transform: scale(0.97);
  transition: transform 160ms ease-out;
}

```

Source: [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md) ("Press feedback", line 59).

### Anchored Popover Entrance

Combine `transform-origin` with scale and opacity for context-aware motion:

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

```

Source: [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md) (line 112).

### List Item Slide-In

Keyframe animations using only GPU properties:

```css
.item {
  opacity: 0;
  transform: translateY(8px);
  animation: fadeIn 300ms ease-out forwards;
}
@keyframes fadeIn {
  to { opacity: 1; transform: translateY(0); }
}

```

Source: [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md) (lines 154-157).

### Hardware-Accelerated Framer Motion

Explicit transform strings ensure GPU execution:

```tsx
<motion.div
  animate={{ transform: "translateX(100px)" }}
  transition={{ duration: 0.2, ease: "easeOut" }}
/>

```

Source: [`AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/AUDIT.md) (line 75).

### Drawer Performance Optimization

Direct per-element transform assignment avoids parent-level style recalculation:

```js
// Inside animation loop
item.style.transform = `translateY(${offset}px)`;

```

Source: [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md) (line 116).

## Key Files in the Repository

| File | Purpose |
|------|---------|
| [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) | Core animation standards, GPU-only rule, press feedback, transform-origin guidance |
| [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md) | Audit checklist flagging layout animation, variable-driven transforms, Framer-Motion shorthands |
| [`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md) | Reviewer guidelines for transition specificity and hardware acceleration |
| [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) | Concrete UI component examples with correct `transform`/`opacity` patterns |
| [`skills/prototype/PICKER.md`](https://github.com/emilkowalski/skills/blob/main/skills/prototype/PICKER.md) | Documented exception (width transition) with justification and overall strategy |

## Summary

- **Animate `transform` and `opacity` only**—these are the sole properties that skip layout and paint, executing entirely on the GPU.
- **Never use `width`, `height`, `top`, `left`, `margin`, or `padding`** in animations or transitions; they force synchronous layout recalculation.
- **Specify exact properties in `transition`** rather than `all` to prevent accidental layout-triggering animations.
- **Use explicit `transform` strings in Framer Motion** instead of `x`/`y`/`scale` shorthands for hardware acceleration.
- **Set `transform` directly on each animated element** rather than driving changes through CSS variables on parents.

## Frequently Asked Questions

### Why are `transform` and `opacity` faster than other CSS properties?

`transform` and `opacity` are **compositor-only properties**: the browser promotes the element to an independent GPU layer and updates only that layer's matrix or alpha value. Other properties require the CPU to recalculate layout (geometry) and repaint pixels before any GPU work can occur. This distinction means GPU-bound animations run at 60+ fps with near-zero main-thread cost.

### Can I ever animate `width` or `height` for performance?

Generally no. The repository documents rare exceptions in [`skills/prototype/PICKER.md`](https://github.com/emilkowalski/skills/blob/main/skills/prototype/PICKER.md) where width transitions are justified with explicit performance mitigation strategies. For all standard UI animations, use `scaleX`/`scaleY` or `transform: scale()` instead—these achieve visual size changes without layout recalculation.

### Does using `will-change` fix layout-triggering animations?

No. While `will-change: transform` or `will-change: opacity` hints to the browser to prepare a compositor layer early, it cannot rescue animations on layout properties. `will-change: width` or `will-change: left` still forces layout recalculation on every frame. The property only helps when applied to already-GPU-friendly properties.

### How do I detect performance-killing animations in code review?

Check [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md) for the official checklist. Key red flags: `transition: all`, Framer Motion `x`/`y`/`scale` props without explicit `transform`, CSS variables driving transforms on multiple children, and any animation keyframes or transitions referencing dimensional or positional properties.