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

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 (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:

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:

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

Correct:

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

Source: 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:

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

Correct:

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

Source: 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:

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

Correct:

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

Source: 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:

.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, lines 59-60.

Practical Implementation Examples

Button Press Feedback

Subtle scale reduction provides tactile feedback without layout impact:

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

Source: STANDARDS.md ("Press feedback", line 59).

Anchored Popover Entrance

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

.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 (line 112).

List Item Slide-In

Keyframe animations using only GPU properties:

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

Source: STANDARDS.md (lines 154-157).

Hardware-Accelerated Framer Motion

Explicit transform strings ensure GPU execution:

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

Source: AUDIT.md (line 75).

Drawer Performance Optimization

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

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

Source: STANDARDS.md (line 116).

Key Files in the Repository

File Purpose
skills/review-animations/STANDARDS.md Core animation standards, GPU-only rule, press feedback, transform-origin guidance
skills/improve-animations/AUDIT.md Audit checklist flagging layout animation, variable-driven transforms, Framer-Motion shorthands
skills/review-animations/SKILL.md Reviewer guidelines for transition specificity and hardware acceleration
skills/emil-design-eng/SKILL.md Concrete UI component examples with correct transform/opacity patterns
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 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 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.

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 →