20 CSS Animated Buttons 01 / 20
CSS Neon Glow Button
A neon-lit CTA button with a pulsing glow halo and colour-shifting outline, built entirely with CSS box-shadow animations and no JavaScript.
The code
<div class="ab-01">
<div class="ab-01__scene">
<p class="ab-01__label">Premium SaaS — Launch Your Project</p>
<h2 class="ab-01__headline">Build faster.<br>Ship smarter.</h2>
<p class="ab-01__sub">Join 50,000+ developers already on the platform.</p>
<button class="ab-01__btn">Get Started Free</button>
<p class="ab-01__hint">No credit card required · Cancel anytime</p>
</div>
</div> <div class="ab-01">
<div class="ab-01__scene">
<p class="ab-01__label">Premium SaaS — Launch Your Project</p>
<h2 class="ab-01__headline">Build faster.<br>Ship smarter.</h2>
<p class="ab-01__sub">Join 50,000+ developers already on the platform.</p>
<button class="ab-01__btn">Get Started Free</button>
<p class="ab-01__hint">No credit card required · Cancel anytime</p>
</div>
</div>.ab-01,.ab-01 *,.ab-01 *::before,.ab-01 *::after{box-sizing:border-box;margin:0;padding:0}
.ab-01 ::selection{background:#b829ff;color:#fff}
.ab-01{
--neon:#b829ff;
--neon-dim:rgba(184,41,255,.35);
--bg:#0a0010;
--text:#e8d5ff;
font-family:system-ui,sans-serif;
background:var(--bg);
min-height:100vh;
display:flex;
align-items:center;
justify-content:center;
}
.ab-01__scene{
text-align:center;
padding:2.5rem 2rem;
max-width:520px;
}
.ab-01__label{
font-size:.72rem;
letter-spacing:.18em;
text-transform:uppercase;
color:var(--neon);
margin-bottom:.75rem;
opacity:.8;
}
.ab-01__headline{
font-size:clamp(1.8rem,4vw,2.6rem);
font-weight:800;
color:#fff;
line-height:1.15;
margin-bottom:.75rem;
}
.ab-01__sub{
font-size:.95rem;
color:var(--text);
opacity:.7;
margin-bottom:2rem;
}
.ab-01__btn{
position:relative;
display:inline-flex;
align-items:center;
gap:.5rem;
padding:.85rem 2.4rem;
font-size:1rem;
font-weight:700;
letter-spacing:.04em;
color:#fff;
background:transparent;
border:2px solid var(--neon);
border-radius:8px;
cursor:pointer;
outline:none;
text-transform:uppercase;
transition:color .3s,background .3s,border-color .3s,box-shadow .3s;
box-shadow:0 0 8px var(--neon-dim),0 0 20px var(--neon-dim),inset 0 0 10px rgba(184,41,255,.08);
animation:ab-01-pulse 2s ease-in-out infinite;
will-change:box-shadow;
text-shadow:0 0 8px rgba(184,41,255,.6);
}
.ab-01__btn:hover{
background:var(--neon);
color:#fff;
box-shadow:0 0 16px var(--neon),0 0 40px var(--neon),0 0 80px var(--neon-dim),inset 0 0 16px rgba(255,255,255,.15);
text-shadow:none;
animation-play-state:paused;
}
.ab-01__btn:active{
transform:scale(.97);
}
@keyframes ab-01-pulse{
0%,100%{box-shadow:0 0 8px var(--neon-dim),0 0 20px var(--neon-dim),inset 0 0 10px rgba(184,41,255,.08)}
50%{box-shadow:0 0 14px var(--neon),0 0 36px var(--neon-dim),0 0 60px rgba(184,41,255,.15),inset 0 0 16px rgba(184,41,255,.12)}
}
.ab-01__hint{
margin-top:1rem;
font-size:.78rem;
color:var(--text);
opacity:.45;
}
@media(prefers-reduced-motion:reduce){
.ab-01__btn{animation:none}
} .ab-01,.ab-01 *,.ab-01 *::before,.ab-01 *::after{box-sizing:border-box;margin:0;padding:0}
.ab-01 ::selection{background:#b829ff;color:#fff}
.ab-01{
--neon:#b829ff;
--neon-dim:rgba(184,41,255,.35);
--bg:#0a0010;
--text:#e8d5ff;
font-family:system-ui,sans-serif;
background:var(--bg);
min-height:100vh;
display:flex;
align-items:center;
justify-content:center;
}
.ab-01__scene{
text-align:center;
padding:2.5rem 2rem;
max-width:520px;
}
.ab-01__label{
font-size:.72rem;
letter-spacing:.18em;
text-transform:uppercase;
color:var(--neon);
margin-bottom:.75rem;
opacity:.8;
}
.ab-01__headline{
font-size:clamp(1.8rem,4vw,2.6rem);
font-weight:800;
color:#fff;
line-height:1.15;
margin-bottom:.75rem;
}
.ab-01__sub{
font-size:.95rem;
color:var(--text);
opacity:.7;
margin-bottom:2rem;
}
.ab-01__btn{
position:relative;
display:inline-flex;
align-items:center;
gap:.5rem;
padding:.85rem 2.4rem;
font-size:1rem;
font-weight:700;
letter-spacing:.04em;
color:#fff;
background:transparent;
border:2px solid var(--neon);
border-radius:8px;
cursor:pointer;
outline:none;
text-transform:uppercase;
transition:color .3s,background .3s,border-color .3s,box-shadow .3s;
box-shadow:0 0 8px var(--neon-dim),0 0 20px var(--neon-dim),inset 0 0 10px rgba(184,41,255,.08);
animation:ab-01-pulse 2s ease-in-out infinite;
will-change:box-shadow;
text-shadow:0 0 8px rgba(184,41,255,.6);
}
.ab-01__btn:hover{
background:var(--neon);
color:#fff;
box-shadow:0 0 16px var(--neon),0 0 40px var(--neon),0 0 80px var(--neon-dim),inset 0 0 16px rgba(255,255,255,.15);
text-shadow:none;
animation-play-state:paused;
}
.ab-01__btn:active{
transform:scale(.97);
}
@keyframes ab-01-pulse{
0%,100%{box-shadow:0 0 8px var(--neon-dim),0 0 20px var(--neon-dim),inset 0 0 10px rgba(184,41,255,.08)}
50%{box-shadow:0 0 14px var(--neon),0 0 36px var(--neon-dim),0 0 60px rgba(184,41,255,.15),inset 0 0 16px rgba(184,41,255,.12)}
}
.ab-01__hint{
margin-top:1rem;
font-size:.78rem;
color:var(--text);
opacity:.45;
}
@media(prefers-reduced-motion:reduce){
.ab-01__btn{animation:none}
}How this works
The glow effect layers multiple box-shadow values on the button — an inset layer for inner luminance plus two outer rings with different blur radii. A @keyframes ab-01-pulse animation oscillates the shadow spread and opacity on a 2 s loop, giving the halo a breathing quality without touching the DOM. The outline is a transparent border that transitions to the accent colour on :hover, creating the classic neon tube look purely via CSS property transitions.
The text colour itself rides a subtle text-shadow transition so the label appears to emit light. will-change: box-shadow promotes the element to its own composite layer, keeping the animation smooth at 60 fps even alongside other page content. The prefers-reduced-motion media query pauses the pulse but preserves the hover glow for accessibility.
Customize
- Change the glow colour by updating
--neon(default#b829ff) — the box-shadow, border, and text-shadow all derive from this one variable. - Control pulse speed by editing
animation-durationon.ab-01__btn(try1sfor urgency or4sfor ambient mood). - Add a second colour stop by duplicating the keyframe and interpolating to a second
--neon-altvariable for a colour-cycling halo. - Increase glow size for hero placements by multiplying the
box-shadowblur values (e.g. change20pxto40pxon the outer ring). - Remove the inner glow for a subtler look by deleting the
inset 0 0 …shadow layer and keeping only the outer rings.
Watch out for
- Very large
box-shadowblur values (> 60px) can trigger sub-pixel painting jitter on Chrome/Windows with certain GPU drivers — test on target hardware. - Safari < 15.4 paints
box-shadowchanges on the main thread; combine withtransform: translateZ(0)to force GPU compositing. - The pulsing halo adds to the stacking context — if the button is inside a
position: relativecontainer withoverflow: hiddenthe outer glow will be clipped.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 69+ | 12+ | 62+ | 69+ |
All features are broadly supported; only extreme blur values vary between GPU drivers.