Why `scale(0)` Creates Unnatural Entrance Animations (And the Correct Alternative)

scale(0) causes unnatural entrance animations because it makes UI elements appear from nothing—violating physical expectations—whereas scale(0.95) combined with opacity: 0 provides a smooth, recognizable starting point that feels realistic.

The emilkowalski/skills repository maintains strict animation standards that explicitly reject scale(0) for entrance effects. These guidelines, documented across multiple skill files, prioritize physical correctness and perceptual continuity in UI motion design.

Why scale(0) Breaks Animation Physics

The core problem with scale(0) is simple: nothing in the physical world materializes from a zero-size state. When an element scales from zero, users experience three distinct issues:

  • No visual cue — Users cannot recognize the element before it fully appears, breaking the expectation that UI elements transition smoothly from a discernible shape.

  • Disrupted spatial continuity — Popovers and dropdowns normally grow outward from their trigger point; scaling from the center severs that anchor, making motion feel detached and artificial.

  • Performance degradation — Scaling from zero forces the browser to recompute layout and paint from an empty box, potentially causing jank on low-end devices.

The repository's animation standards capture this principle bluntly in skills/review-animations/STANDARDS.md#L53:

"Never scale(0). Start from scale(0.9–0.97) + opacity: 0. Nothing in the real world appears from nothing."

The Correct Alternative: Near-Zero Scale + Opacity

The recommended pattern uses minimal scale reduction paired with full opacity transition. This preserves the element's shape as a visual reference while still achieving the entrance effect.

Basic Implementation

.enter {
  transform: scale(0.95);
  opacity: 0;
}

.enter[data-mounted] {
  transition: transform 200ms var(--ease-out),
              opacity 200ms var(--ease-out);
  transform: scale(1);
  opacity: 1;
}

As noted in [skills/improve-animations/AUDIT.md](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md), this pattern addresses a high-severity issue in animation audits. The target range of 0.90–0.97 provides enough visual presence to maintain recognition without compromising the entrance effect.

Anchoring Animations to Their Origin Point

For popovers, dropdowns, and tooltips, the transform origin matters as much as the scale value. These elements should appear to grow from their trigger location, not their own center.

.popover {
  transform-origin: var(--transform-origin); /* Base UI variable */
  transform: scale(0.95);
  opacity: 0;
}

.popover[data-mounted] {
  transition: transform 150ms var(--ease-out),
              opacity 150ms var(--ease-out);
  transform: scale(1);
  opacity: 1;
}

The --transform-origin variable, standard in Base UI implementations, dynamically resolves to the trigger's position. Modals remain the exception—they stay centered, so transform-origin: center is appropriate per STANDARDS.md#L55.

Pure-CSS Entrances with @starting-style

When JavaScript state management isn't required, the CSS @starting-style rule provides a clean, compositor-thread solution. The browser applies starting values instantly, then transitions to the final state.

.toast {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 300ms var(--ease-out),
              transform 300ms var(--ease-out);

  @starting-style {
    opacity: 0;
    transform: translateY(100%);
  }
}

This approach adheres to the repository's performance principle: keep animations on GPU-only properties (transform and opacity). STANDARDS.md#L110 explicitly recommends this pattern for optimal rendering performance.

Complete Reference Examples

/* 1️⃣ Standard entrance: scale from 0.95 + fade */
.fade-scale-enter {
  transform: scale(0.95);
  opacity: 0;
}
.fade-scale-enter[data-mounted] {
  transition: transform 200ms var(--ease-out),
              opacity 200ms var(--ease-out);
  transform: scale(1);
  opacity: 1;
}

/* 2️⃣ Trigger-anchored popover */
.popover {
  transform-origin: var(--transform-origin);
  transform: scale(0.95);
  opacity: 0;
}
.popover[data-mounted] {
  transition: transform 150ms var(--ease-out),
              opacity 150ms var(--ease-out);
  transform: scale(1);
  opacity: 1;
}

/* 3️⃣ No-JS toast with @starting-style */
.toast {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 300ms var(--ease-out),
              transform 300ms var(--ease-out);
  @starting-style {
    opacity: 0;
    transform: translateY(100%);
  }
}

Source Files and Their Roles

File Purpose
[skills/review-animations/STANDARDS.md](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) Defines the "Never scale(0)" rule and provides the scale-and-opacity pattern
[skills/emil-design-eng/SKILL.md](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) Shows concrete before/after examples and explains popover physicality
[skills/improve-animations/AUDIT.md](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md) Lists "Never scale(0)" as high-severity with target values
[skills/find-animation-opportunities/SKILL.md](https://github.com/emilkowalski/skills/blob/main/skills/find-animation-opportunities/SKILL.md) Describes how to identify and replace scale(0) entrances

Summary

  • scale(0) violates physical expectations by making elements appear from nothing, creating unnatural, jarring entrances.

  • Use scale(0.95) to scale(0.97) with opacity: 0 as the starting state—this preserves visual recognition while maintaining the entrance effect.

  • Anchor popovers to their trigger via transform-origin: var(--transform-origin), keeping spatial continuity intact.

  • Prefer @starting-style for pure-CSS implementations to stay on the compositor thread and avoid JavaScript overhead.

  • Reference the repository standards in STANDARDS.md, AUDIT.md, and SKILL.md files for consistent, high-quality animation implementation.

Frequently Asked Questions

What exact scale value should I use instead of scale(0)?

Use between 0.90 and 0.97 depending on the element size and animation speed. Smaller elements (tooltips) benefit from the higher end (0.95–0.97), while larger modals can start slightly smaller (0.90–0.93). The key is maintaining enough visual presence that users recognize the shape before it fully scales in.

Why does scale(0) cause performance problems?

When an element has zero size, the browser must perform full layout recalculation and paint as it scales up, rather than promoting the animation to the compositor thread immediately. The near-zero scale pattern keeps the element's render layer active, allowing the browser to handle the transition via GPU-accelerated transforms alone.

How do I handle modals differently from popovers?

Modals stay centered: use transform-origin: center and scale from 0.95. Popovers and dropdowns anchor to their trigger: use transform-origin: var(--transform-origin) (or calculate the trigger position) so the animation originates from the correct point. This distinction appears in [STANDARDS.md](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) as a physicality requirement for spatial continuity.

When can I use @starting-style instead of data attributes?

Use @starting-style when the entrance doesn't require JavaScript-driven state—static toasts, CSS-only dropdowns, or page-load animations. Stick with [data-mounted] or class toggles when you need explicit mount/unmount control, exit animations, or React/Vue component lifecycle integration.

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 →