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:
-
skills/review-animations/STANDARDS.md(lines 112-113): "Only animatetransformandopacity— they skip layout/paint and run on the GPU." -
skills/improve-animations/AUDIT.md(lines 73-75): Explicitly lists forbidden properties and their rendering impact. -
skills/review-animations/SKILL.md(lines 35-37): "GPU-only properties. Animatetransformandopacityonly. 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:
.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
transformandopacityonly—these are the sole properties that skip layout and paint, executing entirely on the GPU. - Never use
width,height,top,left,margin, orpaddingin animations or transitions; they force synchronous layout recalculation. - Specify exact properties in
transitionrather thanallto prevent accidental layout-triggering animations. - Use explicit
transformstrings in Framer Motion instead ofx/y/scaleshorthands for hardware acceleration. - Set
transformdirectly 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →