Lighting Up Text With CSS Glow Effects

Neon-style text is a striking visual effect that's surprisingly straightforward to build with modern CSS. The core technique relies on the text-shadow property, which lets you stack multiple shadows on a single element by comma-separating them. Each shadow needs four values: horizontal offset, vertical offset, blur radius, and color.

To create a realistic neon glow, you typically need more than one shadow. A single shadow produces a flat, artificial-looking glow. By layering several shadows with different blur radii, you build depth. For instance, you might start with a tight, bright white glow near the letter edges and add progressively wider, softer colored glows on top. The result mimics the way real neon tubes cast light onto nearby surfaces:

.neonText {
  color: #fff;
  text-shadow:
    /* White glow */
    0 0 7px #fff,
    0 0 10px #fff,
    0 0 21px #fff,
}
.neonText {
  color: #fff;
  text-shadow:
    /* White glow */
    0 0 7px #fff,
    0 0 10px #fff,
    0 0 21px #fff,
    /* Green glow */
    0 0 42px #0fa,
    0 0 82px #0fa,
    0 0 92px #0fa,
    0 0 102px #0fa,
    0 0 151px #0fa;
}

The number of shadows required depends on the depth of glow you want. Stacking five or more shadows isn't unusual for a polished effect. Feel free to experiment with different hues and blur sizes — you can even blend two colors by using different colored shadows in the stack.

Making the Sign Flicker

Older neon signs often flicker, and you can replicate that with CSS animations. Using @keyframes, you define an animation that applies and removes the text-shadow stack at different points in the timeline. The keyframes create quick, irregular-looking flashes by alternating between full glow and no glow:

@keyframes flicker {
  0%, 18%, 22%, 25%, 53%, 57%, 100% {
    text-shadow:
      0 0 4px #fff,
      0 0 11px #fff,
      0 0 19px #fff,
      0 0 40px #0fa,
      0 0 80px #0fa,
      0 0 90px #0fa,
      0 0 100px #0fa,
      0 0 150px #0fa;
  }
  20%, 24%, 55% {       
    text-shadow: none;
  }
}

To apply the flicker, attach the animation to the element. For realism, it's often better to apply the flicker to only part of a multi-word sign — say, a single <h1> — rather than the whole thing flickering in unison. If you do want the entire sign to flicker, you can skip defining text-shadow on the base class and let the animation's keyframes own all the shadow definitions.

h1 {
  animation: flicker 1.5s infinite alternate;     
}

Subtle Shifts: Pulsing and Gentle Flicker

Beyond a hard flicker, you can create a pulsating effect by animating the blur radius itself instead of toggling shadows on and off. In the keyframes, you set the smallest blur values at one end and larger values at the other. As the animation plays, the glow expands and contracts smoothly:

@keyframes pulsate {
  100% {
    /* Larger blur radius */
    text-shadow:
      0 0 4px #fff,
      0 0 11px #fff,
      0 0 19px #fff,
      0 0 40px #0fa,
      0 0 80px #0fa,
      0 0 90px #0fa,
      0 0 100px #0fa,
      0 0 150px #0fa;
  }
  0% {
    /* Smaller blur radius */
    text-shadow:
      0 0 2px #fff,
      0 0 4px #fff,
      0 0 6px #fff,
      0 0 10px #0fa,
      0 0 45px #0fa,
      0 0 55px #0fa,
      0 0 70px #0fa,
      0 0 80px #0fa;
  }
}
h1 {
  animation: pulsate 2.5s infinite alternate;     
}

For a more subdued flicker that doesn't flash off completely, reduce the blur radius values in one keyframe instead of removing the shadows entirely. Since the change is subtle, the animation should run faster to feel like genuine flickering. A duration of around 0.11s repeat creates a quick, shivering effect:

@keyframes pulsate {
  100% {
    /* Larger blur radius */
    text-shadow:
      0 0 4px #fff,
      0 0 11px #fff,
      0 0 19px #fff,
      0 0 40px #f09,
      0 0 80px #f09,
      0 0 90px #f09,
      0 0 100px #f09,
      0 0 150px #f09;
  }
 0% {
    /* A slightly smaller blur radius */
    text-shadow:
      0 0 4px #fff,
      0 0 10px #fff,
      0 0 18px #fff,
      0 0 38px #f09,
      0 0 73px #f09,
      0 0 80px #f09,
      0 0 94px #f09,
      0 0 140px #f09;
  }
}
h1 {
  animation: pulsate 0.11s ease-in-out infinite alternate;    
}

Framing It Like a Real Sign

A neon sign rarely floats in space; it hangs on a wall. Add a CSS background image (such as a brick texture) to the page to give the sign context:

body {
  background-image: url(wall.jpg);
}

To make it look like a physical sign, add a border around the container element holding the text — often the <h1> itself. Use the border shorthand to create a solid white outline and give the text breathing room with padding:

h1 {
  border: 0.2rem solid #fff;
  padding: 0.4em;
}

Round the corners with border-radius, then apply the glow to the border. text-shadow won't affect a border, but box-shadow shares a nearly identical syntax. You can copy your glow values and tweak them for the border. Adding the inset keyword puts glow inside the border too, giving realistic depth on both edges:

h1 {
  border: 0.2rem solid #fff;
  border-radius: 2rem;
  padding: 0.4em;
}
h1 {
  border: 0.2rem solid #fff;
  border-radius: 2rem;
  padding: 0.4em;
  box-shadow: 0 0 .2rem #fff,
              0 0 .2rem #fff,
              0 0 2rem #bc13fe,
              0 0 0.8rem #bc13fe,
              0 0 2.8rem #bc13fe,
              inset 0 0 1.3rem #bc13fe;
}

If your sign uses a circular or rectangular border, these same techniques apply — just adjust the border-radius and box-shadow offsets to match the shape.

Respecting User Motion Preferences

Flashing animations can be problematic for some users. The prefers-reduced-motion media query lets you disable or tone down your neon effects for those who request it. If the flicker was applied only to the <h1>, put that override in the media query:

@media screen and (prefers-reduced-motion) { 
  h1 {
    animation: none;
  }
}

This disables the flashing animation entirely for users with that system preference, keeping your sign visually interesting without compromising accessibility. Designing with these preferences in mind is a key part of building functional, user-respecting interfaces.