Attention-seeking animations: pulse, shake, bounce
Three keyframe patterns that say "look here" without pulling the user out of their flow. Each one solves a different communication problem.
When to reach for attention animations
Notification badges need to announce themselves. Form fields need to signal validation errors. Call-to-action buttons need to draw the eye on a busy page. These are all cases where a brief, targeted animation communicates faster than a color change or icon swap alone.
The three most useful attention patterns are pulse (continuous, ambient), shake (one-shot, corrective), and bounce (one-shot or looping, playful). Each maps to a different intent, and each requires a different duration, easing, and repetition strategy.
The pulse pattern
Pulse uses scale and opacity to create a rhythmic breathing effect. It works well on notification dots, live indicators, and recording badges — anywhere you need a persistent "this is active" signal.
@keyframes pulse {
0%, 100% {
opacity: 1;
scale: 1;
}
50% {
opacity: 0.7;
scale: 1.05;
}
}
.notification-dot {
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background: oklch(0.65 0.25 25);
animation: pulse 2s ease-in-out infinite;
}
The 2-second duration keeps the effect ambient rather than urgent. Both scale and opacity animate on the compositor, so this runs at 60fps with no layout cost.
The shake pattern
Shake uses translate to oscillate an element side-to-side. It communicates "something went wrong" — wrong password, missing required field, invalid input. The motion is fast and short.
@keyframes shake {
0%, 100% { translate: 0; }
10%, 50%, 90% { translate: -4px; }
30%, 70% { translate: 4px; }
}
.input--error {
animation: shake 400ms ease-in-out;
border-color: oklch(0.65 0.25 25);
}
Keep shake durations between 300ms and 500ms. Longer shakes feel aggressive. The animation runs once — no infinite — because the feedback is a one-time signal, not a persistent state.
The bounce pattern
Bounce uses translate on the Y axis with a quick return to rest. It works for scroll-down indicators, playful CTAs, and drawing attention to a newly appeared element.
@keyframes bounce {
0%, 100% { translate: 0; }
40% { translate: 0 -12px; }
60% { translate: 0 -6px; }
}
.scroll-indicator {
animation: bounce 1.5s ease-in-out infinite;
}
.cta-button:hover {
animation: bounce 500ms ease-out;
}
For looping bounce (scroll indicators), use 1–2 second durations. For one-shot bounce (hover reactions), 400–600ms feels responsive. The asymmetric keyframe stops at 40% and 60% create a natural deceleration that looks better than a symmetric 50% peak.
Timing and restraint
The biggest mistake with attention animations is overuse. More than one animated element in the viewport at the same time creates visual competition — nothing gets attention when everything demands it.
- One attention animation per viewport. If a notification pulses, the CTA should not bounce simultaneously.
- Never autoplay shake on page load. Shake implies error feedback. Triggering it without user action feels like a bug.
- Pulse loops must be subtle. Keep the
scalechange under 10% and the opacity shift under 30%. - Use
animation-delayto stagger attention. If multiple elements need to animate, offset them so the eye processes each one sequentially.
Respecting prefers-reduced-motion
All three patterns involve repetitive or sudden movement that can trigger discomfort for users with vestibular sensitivities. Wrap attention animations in a motion preference check.
@media (prefers-reduced-motion: reduce) {
.notification-dot,
.scroll-indicator {
animation: none;
}
.input--error {
/* replace shake with a visible border change */
animation: none;
outline: 2px solid oklch(0.65 0.25 25);
outline-offset: 2px;
}
}
The key is not to remove the communication, but to replace motion with a static visual signal. A thicker border or an outline offset conveys the same "error" message without movement. prefers-reduced-motion is supported in Chrome 74+, Safari 10.1+, Firefox 63+.
Practical strategy for 2026
@keyframes and animation have been Baseline since the early 2010s. The individual transform properties (translate, scale) used in these examples are Baseline from 2022 — Chrome 104+, Safari 14.1+, Firefox 72+. There are no compatibility concerns for production use.
@keyframes block does the job.