To add a glow effect in CSS, use box-shadow with a blur radius and a semi-transparent color for boxes and buttons, text-shadow for text, and filter: drop-shadow() for icons and transparent images. Layer two or three shadows for a softer falloff, and animate a pseudo-element's opacity instead of the shadow itself.
That covers most glow requests. The rest of this guide is the detail: which technique fits which element, an animated gradient border with a blurred halo, the stacking bug that hides the halo, and the accessibility checks a glow needs, because forced colors mode removes every shadow. All CSS was validated with csstree-validator and rendered in Chromium on 2026-10-09. For finished glow components you can edit, see the HorizonX glow effects collection.
Which glow technique for which element
| Element | Technique | Why |
|---|---|---|
| Card, panel, button | box-shadow (layered) |
Follows border-radius, cheap when static |
| Heading or short display text | text-shadow |
Follows the glyph shapes |
| Icon, SVG, PNG with transparency | filter: drop-shadow() |
Follows the alpha shape, not the bounding box |
| Border that shifts color or rotates | conic-gradient border plus a blurred pseudo-element |
Shadows cannot hold a gradient |
| Large lit background | Gradient or a shader | Hundreds of shadows cost more than one painted layer |
The distinction between the first and third rows is in the MDN drop-shadow() reference: box-shadow draws a rectangle behind the element's box, while drop-shadow() follows the shape of the image's visible pixels.
Glow around a box with layered box-shadow
A single large shadow looks like fog. Three layers, a thin ring, a tight glow and a wide one, look like light:
:root {
--glow: #8b5cf6;
}
.glow-box {
border-radius: 16px;
background: #0f1020;
box-shadow:
0 0 0 1px color-mix(in oklab, var(--glow) 60%, transparent),
0 0 12px color-mix(in oklab, var(--glow) 45%, transparent),
0 0 40px color-mix(in oklab, var(--glow) 25%, transparent);
}
Zero offsets keep the glow centered. color-mix() derives each layer's transparency from one custom property, so a theme changes one value. box-shadow and color-mix() are both widely available (web-features data, checked 2026-10-09).
Glows need a dark surface. On a white page, a colored shadow reads as a stain rather than light. If your design is light, use a tinted shadow with a small offset and accept that it is a shadow, not a glow.
Glow on hover without animating the shadow
Transitioning box-shadow repaints the element on every frame. The web.dev animations guide advises avoiding "any property that triggers layout or paint unless it's absolutely necessary". The usual fix is to draw the glow once on a pseudo-element and fade its opacity:
.glow-button {
position: relative;
isolation: isolate;
border-radius: 999px;
}
.glow-button::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
box-shadow: 0 0 24px 4px color-mix(in oklab, var(--glow) 55%, transparent);
opacity: 0;
transition: opacity 200ms ease-out;
}
.glow-button:hover::before,
.glow-button:focus-visible::before {
opacity: 1;
}
.glow-button:focus-visible {
outline: 2px solid var(--glow);
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.glow-button::before {
transition: none;
}
}
isolation: isolate makes the button its own stacking context, so z-index: -1 puts the glow behind the button's text but not behind the page. Keyboard focus gets the same glow plus a real outline, for reasons covered in the accessibility section. Under reduced motion the glow still appears, it just does not fade.
Neon text with text-shadow
.neon-title {
color: #fff;
text-shadow:
0 0 2px #fff,
0 0 10px var(--glow),
0 0 28px var(--glow);
}
The white inner layer brightens the glyph edges, the colored layers spread outward. text-shadow supports this list syntax everywhere. Keep neon to headings and short labels: on paragraphs it blurs letter shapes and tires the eye. Check contrast on the text color against the background, not on the glow. The glow does not count toward WCAG contrast, and it disappears in forced colors mode.
Glow for icons and transparent images
.glow-icon {
filter: drop-shadow(0 0 6px var(--glow));
}
Applied to an SVG icon or a PNG logo, the glow traces the outline of the shape. The same rule with box-shadow would draw a glowing rectangle around the image's box.

Animated glowing border with @property
A rotating gradient border is the effect most "glow CSS" searches end up wanting. It needs three pieces: a registered custom property for the angle, a two-layer background that shows the gradient only in the border area, and a blurred copy of the gradient behind the box for the halo.
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.glow-border {
position: relative;
border: 2px solid transparent;
border-radius: 16px;
background:
linear-gradient(#0f1020, #0f1020) padding-box,
conic-gradient(from var(--angle), #8b5cf6, #22d3ee, #f472b6, #8b5cf6) border-box;
animation: spin-border 6s linear infinite;
}
.glow-border::after {
content: "";
position: absolute;
inset: -2px;
z-index: -1;
--angle: inherit;
border-radius: inherit;
background: conic-gradient(from var(--angle), #8b5cf6, #22d3ee, #f472b6, #8b5cf6);
filter: blur(16px);
opacity: 0.55;
}
.glow-stage {
isolation: isolate;
}
@keyframes spin-border {
to {
--angle: 360deg;
}
}
@media (prefers-reduced-motion: reduce) {
.glow-border {
animation: none;
}
}
@media (forced-colors: active) {
.glow-border {
border-color: CanvasText;
}
}
How it works:
- The angle. A plain custom property cannot be animated smoothly, because the browser does not know it holds an angle.
@propertyregisters--anglewith the<angle>syntax, so the keyframes interpolate from 0deg to 360deg and theconic-gradient()rotates. - The border. The solid
linear-gradientpaints the padding box and the conic gradient paints the border box. Only the 2px transparent border lets the conic layer show. - The halo. The
::aftercopy is blurred and sits behind the box. Because--angleis registered withinherits: false, the pseudo-element would otherwise stay at 0deg while the border spins;--angle: inheritkeeps them in sync. In our render, both reported the same angle mid-animation. - The stacking bug. With
z-index: -1and no stacking context in between, the halo can end up behind the background of a parent section and vanish. In our test it did exactly that on a dark section. Addingisolation: isolateto the section (.glow-stageabove) brought it back. Put that class on the element that paints the background behind the card.
@property reached Baseline in July 2024: Chrome 85, Firefox 128 and Safari 16.4 (web-features data, checked 2026-10-09). In an older browser the border shows a static gradient, which is an acceptable fallback.
This border still repaints every frame, since the gradient changes. Use it for one featured element, not a grid of cards, and stop it when it is off screen if the page is long.

Accessibility checks for glow effects
Glows fail in ways that are easy to miss on a designer's screen.
- Forced colors removes shadows. In Windows contrast themes, MDN's forced-colors reference lists
box-shadowandtext-shadowas forced tononeand non-URL background images as removed. A focus state built only from a glow disappears. Keep a realoutlinefor focus, and add a border in aforced-colorsquery where the glow defined the edge, as the example above does. - Focus must be visible without the glow. WCAG 2.4.7 requires a visible keyboard focus indicator, and a soft glow on a dark background is often too faint to count. The outline in the button example is the indicator; the glow is decoration.
- No fast pulsing. A glow that pulses between bright and dark is a flash. WCAG 2.3.1 sets the limit at three flashes in any one second. Slow pulses of a second or more stay well clear.
- Respect reduced motion. Stop rotations and pulses under
prefers-reduced-motion: reduceand leave the static glow. - Contrast is measured without the glow. Text color against background must pass on its own.
Glow components you can adjust instead of hand-tuning
Getting blur radii, color stops and timing right takes iteration. These HorizonX components cover the common glow patterns, and each opens in an editor where you adjust it before exporting. The states each one handles are listed on its page:
- Spectrum Glow Button: a pure CSS button whose spectrum border animates on hover or keyboard focus, with reduced-motion support in the download.
- Neon Edge Button: a neon edge built from blurred strips passed through a filter, with a dot matrix overlay.
- Border Glow: a card whose edge lights up nearest the pointer, with keyboard focus, touch and reduced-motion states.
- Glow Grid: a WebGL wall of tiles lit from behind, for backgrounds. It respects reduced motion, pauses off screen and falls back to plain HTML without WebGL.
FAQ
How do I add a glow effect around a box in CSS?
Use box-shadow with zero offsets and a blur radius, for example box-shadow: 0 0 20px rgb(139 92 246 / 0.5). The glow follows the box's border-radius and grows with the box, so it works on elements whose size depends on content. Layer two or three shadows for a softer edge.
How do I make text glow in CSS?
Use text-shadow with several layers and no offset: a small white blur close to the letters and one or two larger blurs in the glow color. Set a solid text color with enough contrast, since the glow is removed in forced colors mode and does not count toward contrast.
How do I make a glowing button on hover?
Put the glow on a ::before pseudo-element behind the button, start it at opacity: 0, and set opacity: 1 on :hover and :focus-visible. Fading opacity is cheaper than animating the shadow, and the focus state should keep a visible outline as well.
Next step
Start with the technique table, use layered shadows for most cases, and keep animated borders for one element per view. To compare glow buttons, cards and backgrounds you can edit and export, browse the HorizonX glow effects collection.