Neon Glow Effect in CSS: text-shadow, box-shadow and filter Stacks
Master neon glow effects in CSS using layered text-shadow, box-shadow, and filter stacks. Build sign-inspired UI that actually performs well in the browser.
Why Neon Glow Still Works in 2026
Neon glow is one of those effects that should feel dated by now. It peaked in the vaporwave and synthwave aesthetics of the early 2010s, got overused by game studios, and briefly became the CSS equivalent of the spinning cursor. Yet here we are in 2026 and it's everywhere again — in cyberpunk interfaces, dark-mode dashboards, and gaming peripherals sites. It's back because dark UI is back, and nothing punches through a dark background quite like a well-tuned glow.
The reason it keeps returning is also technical: browsers got faster. CSS filters used to tank performance, especially on mobile. These days, GPU compositing handles box-shadow and filter: drop-shadow() smoothly enough that you can animate them without reaching for will-change every five seconds. That said, layering six box-shadows on a 60fps scroll animation is still a bad idea — and we'll cover where the ceiling is.
Honestly, the bigger problem with neon glow isn't performance — it's taste. Most implementations are either too subtle (you squint to see it) or way too aggressive (your eyes water). This article is about getting the middle ground right, using stacked shadow layers and filter tricks that look intentional rather than accidental.
The Core Mechanics: What Actually Creates Glow
Glow in CSS isn't a single property. It's an illusion built from three separate tools that happen to complement each other: text-shadow for type, box-shadow for element edges, and filter: drop-shadow() for arbitrary shapes (including SVG paths and pseudo-elements). You'll often stack all three on the same element.
The basic recipe for any glow is a tight, high-opacity shadow close to the element, then progressively wider and lower-opacity shadows radiating outward. This mimics how real neon tubes or LEDs scatter light — bright right at the source, falling off fast, with a very soft corona at the outer edge. A single box-shadow: 0 0 20px cyan looks flat. Four layered shadows look like an actual light source.
Here's the text glow pattern that gets used most. The first value (0px blur, white) is the bright core. Each subsequent layer adds spread:
``css
.neon-text {
color: #fff;
text-shadow:
0 0 4px #fff,
0 0 12px #fff,
0 0 28px #0ff,
0 0 60px #0ff,
0 0 100px #0af;
}
`
The white inner shadows keep the letterforms crisp. The cyan outer layers create the halo. That 100px` spread is intentional — without a wide diffuse layer the effect reads as a hard outline, not a glow.
Worth noting: text-shadow doesn't accept spread-radius (the third length value that box-shadow has). You compensate by repeating the same color at incrementally higher blur radii. It's verbose but there's no cleaner way.
Box-Shadow Stacks for Glowing Elements
Box-shadow is more flexible than text-shadow because it supports spread-radius and the inset keyword. This means you can create outward glow (the default), inward glow with inset, or combine both for a neon-tube-on-a-surface effect where the element appears lit from inside.
Here's a practical button style. The inset shadow fakes a lit interior. The outer shadows are the ambient scatter:
``css
.neon-button {
background: transparent;
border: 2px solid #ff2d78;
color: #ff2d78;
padding: 12px 28px;
border-radius: 4px;
box-shadow:
inset 0 0 8px rgba(255, 45, 120, 0.4),
0 0 8px rgba(255, 45, 120, 0.6),
0 0 20px rgba(255, 45, 120, 0.4),
0 0 50px rgba(255, 45, 120, 0.2);
transition: box-shadow 0.2s ease;
}
.neon-button:hover {
box-shadow:
inset 0 0 14px rgba(255, 45, 120, 0.6),
0 0 14px rgba(255, 45, 120, 0.9),
0 0 40px rgba(255, 45, 120, 0.6),
0 0 80px rgba(255, 45, 120, 0.3);
}
``
Notice how the hover state doesn't change the color — it just increases opacity and blur radius. That's intentional. Shifting color on hover interrupts the "single light source" illusion.
In practice, four shadow layers is about the sweet spot. Below three and it reads flat. Above five and you start running into paint budget issues, especially on Safari which has historically been slower to composite complex shadow stacks. As of Safari 17 (released 2023) things are much better, but stay conservative if you're stacking shadows on elements that animate.
One more thing — box-shadow glows work best when the element has no background-color or only a very dark one. If you put a bright blue glow on a white card it'll look like a print error, not neon. The effect lives in dark contexts.
Using filter: drop-shadow() for Glow on SVGs and Irregular Shapes
Here's the thing box-shadow can't do: follow non-rectangular shapes. If you have an SVG icon, a diagonal CSS clip-path, or a pseudo-element you want to glow, box-shadow glows around the bounding box — not the actual shape. filter: drop-shadow() follows the alpha channel of the element, so it wraps the real contour.
The syntax mirrors box-shadow minus the spread-radius and inset:
``css
.neon-icon {
filter:
drop-shadow(0 0 4px #0ff)
drop-shadow(0 0 12px #0ff)
drop-shadow(0 0 30px #0af);
}
`
You can chain multiple drop-shadow() calls inside a single filter declaration. Browsers apply them in sequence, so each one glows off the result of the previous — this actually produces a slightly different (and often better) visual than equivalent box-shadow` stacks because you get compound layering rather than independent shadows.
Quick aside: filter creates a new stacking context, which can mess with z-index ordering in complex layouts. If your glowing element suddenly disappears behind something it shouldn't, that's almost certainly the stacking context. The fix is usually to move the filter to a wrapper instead of the element directly.
For SVG icons specifically, you can also define the glow inside the SVG using a <feGaussianBlur> filter primitive. That approach gives you more control and renders identically across all browsers, but it's more markup. CSS drop-shadow() is fine for 95% of cases.
Animating Neon: Flicker, Pulse, and the Buzz Effect
Static glow is nice. Animated glow is immersive. The classic neon sign behaviors — slow pulse, fast flicker, a slight intensity oscillation — are all achievable with CSS keyframes and a bit of shadow manipulation. The trick is animating opacity or shadow values rather than color, because color changes aren't GPU-composited and will cause layout thrashing on lower-end devices.
Here's a pulse animation that slowly intensifies and relaxes the glow:
``css
@keyframes neon-pulse {
0%, 100% {
text-shadow:
0 0 4px #fff,
0 0 14px #f0f,
0 0 40px #f0f;
}
50% {
text-shadow:
0 0 2px #fff,
0 0 8px #f0f,
0 0 20px #f0f;
}
}
.neon-pulse {
animation: neon-pulse 2s ease-in-out infinite;
}
`
For a flicker effect, you want sharp on/off transitions at irregular intervals. The cleanest way to do that is a multi-step keyframe with deliberately jagged timing:
`css
@keyframes neon-flicker {
0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% {
opacity: 1;
}
20%, 24%, 55% {
opacity: 0.2;
}
}
.neon-flicker {
animation: neon-flicker 3s linear infinite;
}
``
That percentage pattern is what makes it feel like a real faulty tube rather than a smooth fade. The erratic spacing at 19%/20%/21% creates that rapid stutter before the main drop at 55%.
Look, if you're building a cyberpunk or vaporwave UI, you'll want these animations on interactive elements — not on everything. Animating every piece of text on a page is genuinely hard to read and triggers motion sensitivity issues. Scope it to hero text, icon accents, and active state indicators. Use prefers-reduced-motion to kill the animation for users who need it:
``css
@media (prefers-reduced-motion: reduce) {
.neon-pulse, .neon-flicker {
animation: none;
}
}
``
One more thing — CSS variables make neon systems much easier to maintain. Define your glow color once at the component or root level and everything updates together:
``css
:root {
--glow-color: #0ff;
--glow-color-dim: rgba(0, 255, 255, 0.3);
}
.neon-element {
box-shadow:
0 0 8px var(--glow-color),
0 0 30px var(--glow-color-dim);
}
``
Performance Ceiling and When to Stop
CSS glow effects are cheap in isolation and expensive in aggregate. A single glowing button? Zero perceptible cost. Sixteen glowing list items, two of which are animating, inside a scrolling container? You'll see jank, especially on mid-range Android devices. Knowing the ceiling matters before you design a whole page around this effect.
The composite budget is roughly: box-shadow and text-shadow promote to their own GPU layer only if they're animating (or if you force it with will-change: box-shadow). Static shadows are painted on the CPU during layout and then composited. That's fine for a handful of elements. It gets expensive when you have many unique shadow values — the browser has to repaint each one individually.
The safe approach for large surfaces is to fake the glow with a background gradient or an absolutely positioned pseudo-element with a blurred background. A ::before pseudo with filter: blur(20px) and a solid colored background looks nearly identical to box-shadow glow at a fraction of the paint cost, because the blur is hardware-accelerated and the pseudo-element becomes its own composite layer:
``css
.neon-card {
position: relative;
background: #111;
border: 1px solid #0ff;
}
.neon-card::before {
content: '';
position: absolute;
inset: -2px;
background: transparent;
border-radius: inherit;
box-shadow: 0 0 40px #0ff;
filter: blur(8px);
opacity: 0.7;
z-index: -1;
}
``
This pattern separates the paint cost from the element itself and lets the browser optimize the layer independently.
You'll find ready-to-use glowing UI components — cards, buttons, inputs — on Empire UI. If you're building a full dark-mode interface and don't want to tune every shadow value by hand, starting from a component library built around these aesthetics saves you a lot of iteration time. The box shadow generator is also genuinely useful for prototyping multi-layer stacks interactively before committing them to code.
Putting It Together: A Neon UI System
A single glowing element is a trick. A system is design. If you're building a neon-heavy interface — think gaming dashboard, music visualizer, dark-mode landing page — you want a small set of glow tokens rather than ad-hoc values scattered everywhere.
Here's a minimal token system that covers most cases:
``css
:root {
/* Primary neon: cyan */
--neon-cyan: #0ff;
--neon-cyan-shadow: 0 0 6px #0ff, 0 0 18px rgba(0,255,255,0.5), 0 0 50px rgba(0,255,255,0.2);
/* Secondary neon: magenta */
--neon-magenta: #f0f;
--neon-magenta-shadow: 0 0 6px #f0f, 0 0 18px rgba(255,0,255,0.5), 0 0 50px rgba(255,0,255,0.2);
/* Accent: hot pink */
--neon-pink: #ff2d78;
--neon-pink-shadow: 0 0 6px #ff2d78, 0 0 18px rgba(255,45,120,0.5), 0 0 50px rgba(255,45,120,0.2);
}
.glow-cyan { box-shadow: var(--neon-cyan-shadow); border-color: var(--neon-cyan); }
.glow-pink { box-shadow: var(--neon-magenta-shadow); border-color: var(--neon-magenta); }
``
Three colors, consistent falloff curve on each, applied via utility classes. Simple to extend, simple to audit.
Worth noting: pair neon glow with a near-black background that has a slight hue bias matching your primary glow color. A pure #000000 black can look flat because the glow has nothing to tint. A #050510 (very dark blue-black) for cyan glow, or #100508 for pink glow, adds atmosphere without being obviously colored.
If you're pulling in a style from the glassmorphism generator or any of the other style hubs, these neon tokens layer on top cleanly. Glassmorphism cards with neon border glow is a particularly effective combination — the frosted surface gives the glow somewhere interesting to scatter. The two aesthetics were practically made for each other.
FAQ
box-shadow follows the rectangular bounding box of the element. filter: drop-shadow() follows the actual alpha channel, so it wraps SVGs, clipped shapes, and irregular outlines correctly. Use box-shadow for rectangular elements, drop-shadow() for everything else.
Four to five layers is the practical ceiling for elements that animate. For static glows you can push to six or seven without noticing a difference, but beyond that you're paying paint cost for marginal visual gain. Test on a mid-range Android device — that's your real benchmark.
Yes, but color in text-shadow and box-shadow is interpolated on the CPU, not composited. Animating the blur radius or opacity instead gets you hardware acceleration and smoother results on mobile.
Technically yes, practically no. Glow effects depend on contrast — a cyan glow needs a dark surface to scatter against. On a white or light gray background it disappears entirely. If you need a light-mode equivalent, use a strong drop shadow with a saturated color offset instead.