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

> Avoid unnatural entrance animations with scale(0). Discover the correct alternative using scale(0.95) and opacity: 0 for a smooth, realistic UI element transition.

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

---

**`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`](https://github.com/emilkowalski/skills/blob/main/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

```css
.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)](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.

```css
.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`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/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.

```css
.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`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md#L110) explicitly recommends this pattern for optimal rendering performance.

## Complete Reference Examples

```css
/* 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)](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)](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)](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)](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`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md), [`AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/AUDIT.md), and [`SKILL.md`](https://github.com/emilkowalski/skills/blob/main/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/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.