Buttons✨ Premium

Neon Pulse

A cyan halo that breathes continuously: three shadows of 5, 10 and 20 px at rest, four shadows of 10, 20, 40 and 80 px at the middle of a 2 s cycle, ease-in-out curve — no hover, no JavaScript. It really does pulse.

CSSAnimationNeon

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Buttons has 27 effects, including 2 free. Explore the category →

3 usage examples

Book now — landing page of a night event — Neon Pulse example 1

① Book now — landing page of a night event

WhenHero of a party or festival: uppercase title, date, venue, a booking button.
WhyThe breathing neon is the page's sign: it draws the eye to the only button while nothing else moves. The dark background is the one pure cyan demands. The sales-page case was on the previous page.
SettingsDuration 2.6s for a slow breath, shadows capped at 60px, letter-spacing: .12em.
Join the stream — live broadcast card — Neon Pulse example 2

② Join the stream — live broadcast card

WhenStream card: thumbnail, “Live” badge, streamer name, a button to join.
WhyA slow pulse is the visual code for “in progress”: the glow says it's happening now, without a counter or an extra animation.
SettingsDuration 1.6s, animation-play-state: paused on hover to freeze the halo when the user aims at the button.
Play again — game-over screen of an arcade machine — Neon Pulse example 3

③ Play again — game-over screen of an arcade machine

WhenScore screen: leaderboard, your rank, a button to restart.
WhyThe arcade aesthetic wants a beating neon; the 80 px halo on a black background fills the screen with a glow without any background image.
SettingsOpaque #0a0a0f button fill for a crisp face at the center of the halo, font-size: 18px, text-transform: uppercase.

How it works

.btn-neon-pulse is an outline button: transparent background, text and border: 2px solid in pure cyan #0ff, a fixed text-shadow: 0 0 10px #0ff, and animation: neonPulse 2s ease-in-out infinite (the shipped code writes it in long form, 2s ease-in-out 0s infinite normal none running neonPulse — the same thing). No :hover or :active rule: the animation is the whole effect, and it doesn't stop when you interact.

The keyframes animate box-shadow. At 0% and 100% (to): three shadows 0 0 5px, 0 0 10px, 0 0 20px; at 50%: four shadows 0 0 10px, 0 0 20px, 0 0 40px, 0 0 80px. The lists differ in length: the browser pads the shorter one with a zero, transparent shadow, so the fourth layer fades in while its blur grows from 0 to 80 px. Measured in Chromium: at ≈ 250 ms into the swell, blurs of 8, 16, 32 px and a 4th shadow at 49 px with 0.62 opacity; at ≈ 760 ms, 9.9 / 19.9 / 39.8 px and 79 px at 0.99. The halo doubles in radius and gains an 80 px pool, then recedes: it pulses.

ease-in-out applies to each half: the halo swells for 1 s, shrinks for 1 s, slowing at both extremes — a 0.5 Hz breath, not a blink. The 10 px text-shadow doesn't take part: only the box's halo varies, the text keeps a constant glow.

Rendering and cost: a box-shadow with 80 px of blur is repainted every frame, on the main thread — not GPU-composited like opacity or transform. Harmless for one or two buttons, noticeable on a grid (see FAQ). The overflow: hidden inherited from .demo-btn doesn't clip the button's own shadow — it only clips its descendants — so the halo extends up to ≈ 80 px outside the button: leave room, and a parent without overflow: hidden.

Accessibility

  • Contrast: #0ff on #0a0a0f = 15.8:1 for text and border alike (AAA, and far above the 3:1 required for an outline). But the background is transparent and pure cyan only holds on dark: 8.3:1 on #334155, 1.25:1 on white — illegible. On a light page, #0e7490 for text and border (5.4:1); the cyan halo then becomes an accessory, the neon effect assumes darkness.
  • prefers-reduced-motion not handled — and the animation loops forever. Add @media (prefers-reduced-motion: reduce) { .btn-neon-pulse { animation: none; box-shadow: 0 0 5px #0ff, 0 0 10px #0ff, 0 0 20px #0ff } }. The explicit box-shadow is essential: the base rule declares none, and animation: none alone makes the whole halo vanish.
  • WCAG 2.2.2 (Pause, Stop, Hide): motion that starts on its own and lasts more than 5 s must be stoppable — the pulse is infinite. Provide a pause mechanism (animation-play-state: paused through a class or on hover) or limit the iteration count. At 0.5 Hz we're far from the 3 Hz threshold of 2.3.1 (flashes), but a glow that breathes permanently remains a distraction for attention disorders.
  • Focus: the code doesn't touch outline, but the native ring drowns in a 20 to 80 px cyan halo. Declare .btn-neon-pulse:focus-visible { outline: 2px solid #fff; outline-offset: 4px } for an indicator that stands apart from the neon.
  • Screen readers: nothing generated, no name change; native <button>. Touch: the animation doesn't depend on hover, the effect is identical on mobile — at the cost of a permanent repaint, hence battery. Target 54 px tall (16 + 18 + 16 + 2 × 2), above the 44 px minimum.

Browser compatibility

Requires @keyframes, multiple shadows and box-shadow interpolation between two lists of different lengths — every browser since 2012; older ones jump from one list to the other without a fade. No JavaScript, zero dependencies.

Chrome 26+✓ Full
Firefox 16+✓ Full
Safari 9+✓ Full
Edge 12+✓ Full
Mobile iOS✓ Full (continuous animation)
Android Chrome✓ Full (continuous animation)

Without CSS animations, the halo doesn't exist: the base rule declares no box-shadow. The button keeps its border and its text's fixed glow — legible, but no neon.

The code

HTML structure to paste into your page (the CSS + JS come with Premium):

index.html — structure
<button class="demo-btn btn-neon-pulse">Neon Pulse</button>
Unlock the full code — $9.90/mo incl. tax

The full HTML + CSS + JS of Neon Pulse, ready to paste — plus all 840 effects. Or $299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
2s (CSS — animation-duration) 2s One full cycle (swell + recede). 1.2s: nervous; 3s: slow breath. Below 0.7 s you're approaching a blink.
#0ff (CSS — color, border, text-shadow, and the 7 keyframe shadows) #0ff × 10 The color is repeated ten times, seven of them inside @keyframes neonPulse: a second color on the same page needs a second @keyframes. For neon pink: #ff00ff (6.3:1 on #0a0a0f); for green: #22c55e (8.7:1).
Radii 5 / 10 / 20 px → 10 / 20 / 40 / 80 px (CSS — 0% and 50% keyframes) ×2 + an 80 px pool Pulse amplitude. Remove the 4th shadow for a halo that doubles without the pool; raise it to 120px for a glow that lights the whole card.
ease-in-out (CSS — animation-timing-function) ease-in-out linear: mechanical; steps(2, jump-none): hard blink between the two states — not recommended (2.3.1).
animation-play-state (CSS — to add) running .btn-neon-pulse:hover { animation-play-state: paused } freezes the halo on the current frame when the user aims at the button; through an .is-paused class, it's the pause control WCAG 2.2.2 requires.
background: 0 0 (CSS — .btn-neon-pulse) transparent #0a0a0f: an opaque face, crisp at the center of the halo, that keeps the text contrast whatever lies behind. rgba(0,255,255,.06): a light glass tint.
text-shadow: 0 0 10px #0ff (CSS — .btn-neon-pulse) fixed, 10 px Add it to both keyframes (10px → 18px) so the letters breathe with the halo; remove it for crisp text over a blurred halo.

FAQ

Each button repaints an 80 px blurred shadow 60 times a second — twelve buttons means twelve repaints per frame, on the main thread. Measure in the Performance tab; if it saturates, replace the box-shadow animation with a ::before carrying the four maximum shadows whose opacity you animate from .3 to 1: same breath, on a composited layer, nearly free.
On hover: .btn-neon-pulse:hover { animation-play-state: paused }. After a number of cycles: animation-iteration-count: 3 — but since the base rule has no box-shadow, the halo vanishes at once when it ends; add box-shadow: 0 0 5px #0ff, 0 0 10px #0ff, 0 0 20px #0ff to .btn-neon-pulse so it stays at the rest state.
An ancestor has overflow: hidden (card, column, grid container): it clips the button's shadow, which spills up to 80 px. The button's own overflow: hidden has nothing to do with it — it only concerns its children. Leave 80 px of inner padding in the parent, switch it to overflow: visible, or lower the largest shadow to 40px.