Attention animations are a budget you can overspend
These effects work by exploiting the eye's sensitivity to movement — which means their power comes entirely from scarcity. One pulsing element on a screen is impossible to miss; three pulsing elements compete and none of them wins. Treat attention animation as a budget of roughly one per view.
The same logic applies over time. A pulse that runs forever stops being noticed within seconds and becomes background noise, or worse, an irritation the user actively tries to ignore. Loop only where the state genuinely persists — a live indicator, an unread badge — and use finite animations everywhere else.
Matching the effect to the message
Shake is the universal "no": use it for failed validation and rejected actions, where its negative connotation is exactly right. Never use it to draw attention to something positive — the association is too strong.
Pulse and heartbeat read as "alive" or "waiting for you" — good for notification counts, live status and primary calls to action. Tada, jello and wobble are celebratory or playful, which makes them right for success states and character-driven brands, and jarring in serious or professional contexts.
Bounce and swing imply physical weight, which suits scroll indicators and hanging elements where the metaphor makes sense.
Never rely on motion alone
Motion is not an accessible signal by itself. Users who have enabled reduced motion won't see it, screen reader users never do, and anyone glancing away misses it entirely. Every attention animation must be paired with something durable — a text message, a colour change, an icon, a count.
Validation is the clearest example: a field that only shakes tells a screen reader user nothing. Pair it with an error message, aria-invalid and aria-describedby, and the shake becomes a helpful enhancement rather than the entire signal.
Triggering and repeating cleanly
Finite animations only run once per class application. To replay one — a shake on each failed submit — remove the class, force a reflow, then re-add it; or listen for animationend and remove the class there so the next trigger works. Both patterns are shown in the code for each effect.