Home / Blog / HTML & CSS

HTML & CSS

Animate a Radial Gradient with a Pulsing Halo

Animate a radial-gradient halo on a separate layer, keep text stationary, add a keyboard pause control, and disable decorative motion when the user requests it.

Animate the gradient’s layer instead of its text

To animate a radial gradient as a pulsing halo, paint the gradient on a pseudo-element and animate that layer’s scale and opacity. The radial color stops remain unchanged while their painted surface grows and fades. This avoids moving the text or assuming that arbitrary background-image values interpolate smoothly.

A separate decorative layer is especially useful for a compact feature panel. It can extend beyond the content while the panel clips its edges. Keep the decoration behind a solid reading surface if its brightness changes substantially, so the text does not depend on the animation’s current frame.

Build the halo and a native pause control

The checkbox pauses the animation through the adjacent component’s CSS. Its label stays outside the decorative panel. A keyboard user can focus the checkbox and press Space; no custom click handler is required. When placing several copies on a page, give every checkbox and matching label a unique identifier.

<div class="halo-demo">
  <input class="halo-toggle" id="pause-halo" type="checkbox">
  <label for="pause-halo">Pause halo animation</label>
  <section class="halo-panel">
    <div class="halo-copy">
      <h2>A quiet pulse</h2>
      <p>The words stay still.</p>
    </div>
  </section>
</div>
.halo-demo { max-width: 40rem; margin: 1rem auto; }
.halo-toggle:focus-visible { outline: 3px solid #111827; }
.halo-panel {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  margin-top: .75rem;
  padding: 2rem;
  background: #0f172a;
}
.halo-panel::before {
  content: "";
  position: absolute;
  inset: -25%;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(circle, #38bdf8 0%, rgb(56 189 248 / 0) 60%);
  transform: scale(.8);
  opacity: .4;
  animation: halo-pulse 5s ease-in-out infinite alternate;
}
.halo-copy {
  position: relative;
  z-index: 1;
  padding: 1rem;
  color: #ffffff;
  background: #0f172a;
  overflow-wrap: anywhere;
}
@keyframes halo-pulse {
  to { transform: scale(1.1); opacity: .9; }
}
.halo-toggle:checked ~ .halo-panel::before { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .halo-panel::before { animation: none; transform: scale(1); opacity: .5; }
}

The negative inset makes the decorative layer larger than the panel before scaling. Overflow is clipped at the panel edges, so the growing halo should not create a horizontal scrollbar. The copy wrapper’s stacking level keeps the reading surface above the animated image without relying on a negative z-index.

Tune scale, opacity and duration separately

Scale controls the apparent spread of the halo. Opacity controls how strongly its color contributes over the dark panel. Duration controls how long the outward pulse takes; alternate then reverses it for the return. Change one of these first so you can see whether the problem is size, brightness or speed.

For a second palette, replace both occurrences of the cyan channels with a matching rose color, such as #fb7185 and rgb(251 113 133 / 0). Keeping the transparent endpoint in the same color family makes the source’s intent clear. The resulting halo should change color while the text surface stays dark and stationary.

Use a static preview for palette selection

The CSS Gradient Generator can show a centered two-color radial gradient. Select Radial and compare candidate colors before writing the layered component. Its radial mode hides the angle field because a centered circular gradient does not use a linear direction in this tool.

The exported declaration does not include transparency, scale animation, markup or a pause control. Add those in your stylesheet as shown above. A two-color static generator cannot determine whether a five-second pulse is appropriate in the context of your page or whether several animated panels become distracting together.

Check animation states and preference changes

Inspect the pseudo-element at its start, midpoint and outermost scale. The panel’s dimensions and the text’s position should remain constant. Pause halfway through and verify that the effect stops without jumping to the first frame; resume and check that it continues from the same point.

Enable reduced motion and confirm that the static halo remains visible without scaling. That preference should take effect independently of the checkbox. MDN documents the reduced-motion query; the transform reference explains the separate coordinate transformation applied to the decorative layer.

Finally, test the panel at a narrow width and with larger text. If the halo clips attractively but the text does not fit, fix the content layout rather than enlarging the animation surface. Decorative motion should be optional to understanding the panel: all meaningful information must remain available in its static state.

Do not present this perpetual pulse as evidence that a real operation is still running. If the component represents a download or calculation, connect its visible status to that operation and provide a textual outcome. A CSS animation continues on its own unless your application explicitly changes or removes it.