# Pulsing buttons, glowing badges and animations that never stop

Source: https://easeweb.dev/learn/endless-animations
Topics: Time limits and motion > Pulsing, glowing and spinning decorations stop by themselves (WCAG 2.2.2)
The fix: Give the animation an end: a few cycles that are over within five seconds, written inside the no-preference media query. Or take the animation away and let the button stand out by its size, colour and place, or move it only once, when someone points at it or tabs to it. A decoration needs no Pause button, because it doesn't need to keep moving at all.
Test with: reduced motion, keyboard, mouse, zoom
References: https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html https://www.w3.org/WAI/WCAG22/Techniques/general/G152 https://www.w3.org/WAI/WCAG22/Techniques/general/G11 https://developer.mozilla.org/en-US/docs/Web/CSS/animation-iteration-count https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/repeatCount

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

The home page of a project-planning app has one big button, "Start free trial", beside the price and three lines on what the plan includes. To make sure nobody misses it, the button pulses: a ring spreads out from it and fades, every second and a half, from the moment the page loads for as long as it stays open. It is a few lines of CSS, copied from a "pulse button" snippet, and the snippet ends with `infinite`. The same thing turns up all over the web in other shapes: a "New" badge that glows, an arrow that bounces to say "scroll down", a chat bubble that wobbles, a skeleton placeholder that shimmers after the content has loaded, a decorative icon that spins.

## Why it fails

Success criterion 2.2.2 Pause, Stop, Hide applies to moving, blinking or scrolling information that starts by itself, lasts more than five seconds, and is shown alongside other content. The pulse starts as the page loads, never ends, and sits beside the very text the visitor is reading, so the page needs a way to pause, stop or hide it, and it has none. Understanding 2.2.2 counts any content that conveys a sense of motion, animations included, and draws no line between information and decoration: the point is that the movement distracts from the rest of the page, whatever it is for. The criterion's own way out is the five seconds. Movement that stops by itself within five seconds needs no control, and techniques G11 and G152 describe exactly that: blinking that ends in under five seconds, and animated images set to stop after a few cycles. For a decoration, that is the easy fix.

## Who is affected

The button is the one thing on the page designed to be looked at, so it sits right in the middle of what people read: the price, the plan, the terms.

## What automation and AI miss

The audit finds every running CSS animation, script animation and animated SVG that repeats without end or for more than five seconds, and flags it for a person to check, with its timing and whether a Pause button is near. It can't tell whether the movement is decoration or information, or whether it is essential, so it is a lead, not a finding. It doesn't see an animation a script starts again every few seconds by adding a class, or an animated GIF. AI tools usually wrap the pulse in a reduced-motion media query and stop there, which helps only the people who found that setting, or suggest a Pause button, which is far more than a decoration needs. Many "attention-grabbing button" snippets online end in `infinite`, so AI-written CSS often brings the failure with it.

## Before: a button that pulses forever

```html
<a class="cta" href="/signup">Start free trial</a>
```

```css
.cta {
  display: inline-block;
  padding: 0.75rem 1.5rem;
  border-radius: 0.5rem;
  background: #1d4ed8;
  color: #fff;
  font-weight: 700;
  animation: pulse 1.5s ease-out infinite;
}
@keyframes pulse {
  from { box-shadow: 0 0 0 0 rgb(29 78 216 / 0.6); }
  to { box-shadow: 0 0 0 16px rgb(29 78 216 / 0); }
}
```

`infinite` makes the ring spread again every 1.5 seconds for as long as the page is open, for every visitor, and nothing on the page stops it.

## After: three pulses, then still

```css
.cta {
  display: inline-block;
  padding: 0.75rem 1.5rem;
  border-radius: 0.5rem;
  background: #1d4ed8;
  color: #fff;
  font-weight: 700;
}
/* Three pulses, 4.5 seconds in all, and only for people who haven't asked for less motion. */
@media (prefers-reduced-motion: no-preference) {
  .cta { animation: pulse 1.5s ease-out 3; }
}
@keyframes pulse {
  from { box-shadow: 0 0 0 0 rgb(29 78 216 / 0.6); }
  to { box-shadow: 0 0 0 16px rgb(29 78 216 / 0); }
}
```

One value changes: `infinite` becomes `3`. Three cycles of 1.5 seconds are over in 4.5 seconds, so the pulse still draws the eye as the page opens, then the button rests and the visitor can read. It is written inside `no-preference`, so people who have asked for less motion never see it. This holds only while nothing starts the animation again: a script that re-adds the class every few seconds, or on every scroll, turns it back into movement without end. Keep cycles × duration under five seconds if you change either.

## After: a still button that stands out by itself

```css
.cta {
  display: inline-block;
  padding: 0.875rem 1.75rem;
  border-radius: 0.5rem;
  background: #1d4ed8;
  color: #fff;
  font-size: 1.125rem;
  font-weight: 700;
  box-shadow: 0 0 0 4px rgb(29 78 216 / 0.2);
}
```

Nothing moves, so 2.2.2 has nothing to ask for. The button earns attention the way the rest of the page does: it is larger than anything near it, the only filled button in the colour, with a soft halo standing in for the pulse's ring, and placed where the eye ends after the price. It costs nothing to run and nobody has to find a setting.

## After: movement only on hover and focus

```css
.cta {
  display: inline-block;
  padding: 0.75rem 1.5rem;
  border-radius: 0.5rem;
  background: #1d4ed8;
  color: #fff;
  font-weight: 700;
}
/* One pulse when the pointer arrives or Tab lands, never a loop. */
@media (prefers-reduced-motion: no-preference) {
  .cta:hover,
  .cta:focus-visible { animation: pulse 1.5s ease-out; }
}
@keyframes pulse {
  from { box-shadow: 0 0 0 0 rgb(29 78 216 / 0.6); }
  to { box-shadow: 0 0 0 16px rgb(29 78 216 / 0); }
}
```

The pulse no longer starts by itself: it answers the visitor's own pointer or Tab press, so 2.2.2 does not apply. It runs once and then waits, even while the pointer stays on the button. Movement that interaction starts is the subject of 2.3.3, so it is also written inside `no-preference`. The pulse adds to the focus ring; it never replaces it.

## Which option to use

All three pass 2.2.2. Three pulses keep what the design asked for, attention as the page opens, by changing one value, and the stop is built in. The still button costs nothing and distracts nobody, but the design has to do the work with size, colour and space instead. Movement on hover and focus never pulls anyone's eye from across the page, which is its point and its cost: it gives feedback, not attention. Choose three pulses when the motion is part of the brief, the still button when you can change the design, and hover and focus when the motion was only ever meant as feedback.

## Implementation decisions

Count the five seconds from when the movement starts, across all its cycles: `animation-iteration-count` times `animation-duration`, plus any `animation-delay` between repeats that a script adds. A pulse with a two-second delay and three 1.5-second cycles moves for 4.5 seconds, which passes.

The reduced-motion query is no substitute for an end. Many people who are distracted by movement never find that setting, and 2.2.2 asks for the page itself to stop. Writing the motion inside `no-preference` is still worth doing, because it costs nothing and helps people who have asked; see [animations that ignore the reduced-motion setting](/learn/reduced-motion).

The same fixes apply to every decoration that loops. Each needs an end, or no motion at all:

- A "New" or "Sale" badge that glows or blinks: a few cycles, or a still badge in a stronger colour.
- A "scroll down" arrow that bounces: two or three bounces, then still.
- A skeleton placeholder that shimmers: stop the shimmer when the content arrives, and fall back to a still grey block if loading takes long.
- An SVG icon drawn with `<animateTransform repeatCount="indefinite">`: give `repeatCount` a number, or use a still icon.
- An animated GIF or Lottie file that loops: export it to loop only a few times, or stop it from the player's own setting.

A spinner that shows something is loading is different. Understanding 2.2.2 treats a loading animation as essential while nothing on the page can be used, since it isn't shown alongside other content. A spinner beside content people can read, such as one in a button or a panel, has to stop when the loading does. If it can keep spinning for more than five seconds there, it falls under 2.2.2 like any other movement: after a few seconds, swap it for a still status in words, such as "Still loading…", or a progress bar that moves only as the work does.

Text that moves, such as a ticker or a rotating carousel, can't end within five seconds and still be read, so it needs a Pause button or no movement at all; that has its own guide: [tickers, carousels and animations that can't be paused](/learn/moving-content).

## Verify the fix

1. Load the page and wait five seconds without touching anything. Confirm nothing decorative is still pulsing, glowing, bouncing, shimmering or spinning.
2. In the browser's developer tools, open the Animations panel or check the computed `animation-iteration-count`, and search the CSS and SVG for `infinite` and `indefinite`. Every one left has a Pause button beside it or is essential.
3. Point at the button, then Tab to it. Any movement runs once and stops, even while the pointer or focus stays.
4. Turn on Reduce motion in the operating system and reload. Nothing decorative moves at all.
5. Read the text beside the button in full, also at 200% zoom, and confirm the button still reads as the main action once still.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the design, the CSS framework or the animation library changes.

## Limits

This is a starting pattern, not a guarantee for every application. It covers movement only: flashing more than three times in any one second is a separate check under 2.3.1, made with a flash analysis tool rather than by eye. Whether an animation is decoration or information, and whether the page is calm enough once it is still, still needs a person to judge.
