# Match Motion to Frequency

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

> SHOULD: Match motion to how often an action happens. Keyboard-initiated and 100+/day actions get no animation; reserve delight for rare or first-time moments.

Match motion to how often an action is seen — frequent and keyboard-initiated actions get no animation

Animation that delights during a first-run onboarding becomes friction on an action performed hundreds of times a day. Give high-frequency and keyboard-driven actions instant feedback; reserve motion for rare or first-time moments.

## Bad — do not do this

`animations-emil-frequency-bad`

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

export function EmilFrequencyBad() {
  const [liked, setLiked] = useState(false);
  const [pulse, setPulse] = useState(0);

  return (
    <div className="space-y-4">
      <p className="text-sm text-muted-foreground">Liking is a high-frequency action. Click it a few times.</p>
      <button
        onClick={() => {
          setLiked((v) => !v);
          setPulse((p) => p + 1);
        }}
        className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-muted text-foreground"
      >
        <span
          key={pulse}
          className={`text-xl leading-none ${liked ? 'text-destructive' : 'text-muted-foreground'}`}
          style={{ animation: 'emilLikeBounce 400ms cubic-bezier(0.34, 1.56, 0.64, 1)', display: 'inline-block' }}
        >
          {liked ? '♥' : '♡'}
        </span>
        <span className="text-sm">{liked ? 'Liked' : 'Like'}</span>
      </button>
      <style>{`
        @keyframes emilLikeBounce {
          0% { transform: scale(1); }
          40% { transform: scale(1.6); }
          70% { transform: scale(0.85); }
          100% { transform: scale(1); }
        }
      `}</style>
      <p className="text-xs text-destructive">
        A 400ms bounce on every click turns a constant action into a nuisance
      </p>
    </div>
  );
}
```

## Good — do this

`animations-emil-frequency-good`

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

export function EmilFrequencyGood() {
  const [liked, setLiked] = useState(false);
  const [everLiked, setEverLiked] = useState(false);
  const [celebrating, setCelebrating] = useState(false);
  const [count, setCount] = useState(0);

  const handleClick = () => {
    const next = !liked;
    setLiked(next);
    if (next) {
      setCount((c) => c + 1);
      // Delight, but only for the rare first-ever like.
      if (!everLiked) {
        setEverLiked(true);
        setCelebrating(true);
      }
    }
  };

  const reset = () => {
    setLiked(false);
    setEverLiked(false);
    setCelebrating(false);
    setCount(0);
  };

  return (
    <div className="space-y-4">
      <p className="text-sm text-muted-foreground">
        The first like gets delight. Every like after that is instant.
      </p>
      <div className="flex items-center gap-2">
        <button
          onClick={handleClick}
          className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-muted text-foreground"
        >
          <span
            onAnimationEnd={() => setCelebrating(false)}
            className={`text-xl leading-none transition-colors duration-100 ease-out ${
              liked ? 'text-destructive' : 'text-muted-foreground'
            }`}
            style={
              celebrating
                ? { animation: 'emilLikeCelebrate 450ms cubic-bezier(0.34, 1.56, 0.64, 1)', display: 'inline-block' }
                : undefined
            }
          >
            {liked ? '♥' : '♡'}
          </span>
          <span className="text-sm">{liked ? 'Liked' : 'Like'}</span>
        </button>
        <button
          onClick={reset}
          className="px-3 py-2 rounded-lg bg-muted/60 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
        >
          Reset
        </button>
      </div>
      <p className="text-xs text-muted-foreground tabular-nums">
        Liked {count} {count === 1 ? 'time' : 'times'}
      </p>
      <style>{`
        @keyframes emilLikeCelebrate {
          0% { transform: scale(1); }
          40% { transform: scale(1.6); }
          70% { transform: scale(0.9); }
          100% { transform: scale(1); }
        }
      `}</style>
      <p className="text-xs text-success">
        Delight on the rare first time, instant on every repeat — motion scaled to how often the action actually happens
      </p>
    </div>
  );
}
```

## References

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