# Asymmetric Enter and Exit

**SHOULD** · **ID:** `animations-emil-asymmetric` · **Category:** animations
**Source:** [Emil Kowalski](https://emilkowalski.com/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/animations-emil-asymmetric

> SHOULD: Use asymmetric timing: deliberate user actions animate slower, system responses and exits snap. Symmetric enter/exit timing makes dismissals feel sluggish.

Deliberate actions animate slower; system responses and exits snap

Symmetric timing makes dismissals feel sluggish. Let user-initiated, deliberate motion take its time, and let the system snap back quickly so the interface always feels responsive.

## Bad — do not do this

`animations-emil-asymmetric-bad`

```tsx
import { useState } from 'react';

export function EmilAsymmetricBad() {
  const [show, setShow] = useState(false);

  return (
    <div className="space-y-4">
      <button
        onClick={() => setShow((v) => !v)}
        className="px-4 py-2 rounded-lg bg-primary text-primary-foreground transition-colors duration-150 ease-out hover:bg-primary/90"
      >
        {show ? 'Dismiss' : 'Show toast'}
      </button>
      <div className="min-h-[3rem]">
        <div
          className="inline-block p-3 rounded-lg bg-muted text-sm"
          style={{
            transition: 'transform 500ms cubic-bezier(0.4, 0, 0.2, 1), opacity 500ms cubic-bezier(0.4, 0, 0.2, 1)',
            transform: show ? 'translateY(0)' : 'translateY(8px)',
            opacity: show ? 1 : 0,
            pointerEvents: show ? 'auto' : 'none',
          }}
        >
          Saved to your library
        </div>
      </div>
      <p className="text-xs text-destructive">
        Enter and exit both take 500ms; dismissing feels sluggish
      </p>
    </div>
  );
}
```

## Good — do this

`animations-emil-asymmetric-good`

```tsx
import { useState } from 'react';

export function EmilAsymmetricGood() {
  const [show, setShow] = useState(false);

  return (
    <div className="space-y-4">
      <button
        onClick={() => setShow((v) => !v)}
        className="px-4 py-2 rounded-lg bg-primary text-primary-foreground transition-colors duration-150 ease-out hover:bg-primary/90"
      >
        {show ? 'Dismiss' : 'Show toast'}
      </button>
      <div className="min-h-[3rem]">
        <div
          className="inline-block p-3 rounded-lg bg-muted text-sm"
          style={{
            // Deliberate entrance, snappy exit.
            transition: show
              ? 'transform 300ms cubic-bezier(0.16, 1, 0.3, 1), opacity 300ms cubic-bezier(0.16, 1, 0.3, 1)'
              : 'transform 120ms ease-out, opacity 120ms ease-out',
            transform: show ? 'translateY(0)' : 'translateY(8px)',
            opacity: show ? 1 : 0,
            pointerEvents: show ? 'auto' : 'none',
          }}
        >
          Saved to your library
        </div>
      </div>
      <p className="text-xs text-success">
        Enter is deliberate (300ms), exit snaps (120ms); the UI always feels responsive
      </p>
    </div>
  );
}
```

## References

- [Emil Kowalski — review-animations](https://github.com/emilkowalski/skills/tree/main/skills/review-animations)
