How to Make a “Neon Glow” Effect in CSS

Neon glow is one of those effects that immediately shifts a plain UI toward something memorable. In this guide you will build a crisp, layered neon glow using only CSS. You will create glowing text, a circular neon badge, and a button with a neon arrow. By the end you will know how to stack shadows for depth, use pseudo-elements to add bloom, and tune animations that feel like real signage.

Why Neon Glow Matters

Designers often reach for images or SVG filters to fake light and bloom. Pure CSS neon stays sharp at any size, adapts to theming with custom properties, and keeps your asset pipeline simple. You will style live text and standard elements, so the output remains selectable, accessible, and easy to maintain. Neon works well on icons and geometric components too, so it pairs nicely with shape-based UI elements.

Prerequisites

You do not need a graphics editor for this. You only need comfort with basic CSS and a willingness to stack a few effects with intention.

  • Basic HTML
  • CSS custom properties
  • CSS pseudo-elements (::before / ::after)

Step 1: The HTML Structure

The markup contains a wrapper, a headline for the neon text, a call-to-action button with an arrow, and a circular badge that reads “OPEN.” This structure keeps everything semantic while giving us hooks for layered glow. The badge uses a span for text so we can style the glow independently from the shape.

<!-- HTML -->
<main class="demo">
  <h1 class="neon-title">CSS Neon Glow</h1>

  <a href="#" class="neon-btn">Get Started</a>

  <div class="neon-badge" aria-label="Open">
    <span>OPEN</span>
  </div>
</main>

Step 2: The Basic CSS & Styling

Start with a dark backdrop so the glow reads well. Define neon colors as CSS variables, then set typography and layout. The variables allow you to swap hues without touching the component rules, which makes theme experiments quick.

/* CSS */
:root {
  --bg: #080d16;
  --text: #e6f3ff;

  --neon-cyan: #00e5ff;
  --neon-magenta: #ff00ff;
  --neon-green: #39ff14;

  --glow-weak: 6px;
  --glow-med: 14px;
  --glow-strong: 28px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html, body {
  height: 100%;
}

body {
  margin: 0;
  background: radial-gradient(1200px 800px at 70% 20%, #0f1730 0%, var(--bg) 60%);
  color: var(--text);
  font: 500 16px/1.5 system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
}

.demo {
  min-height: 100%;
  display: grid;
  place-items: center;
  gap: 2.4rem;
  padding: 4rem 1.5rem;
}

Advanced Tip: Keep glow radii in variables too. When you scale a component up or down, bumping –glow-weak, –glow-med, and –glow-strong together keeps the light falloff consistent.

Step 3: Building the Neon Text

Neon text starts with bright foreground color and layered text-shadow. Text-shadow can stack many times, which lets you fake the hot core and softer bloom. Add a subtle outer bloom with filter drop-shadow on a pseudo-element for extra separation from the background.

/* CSS */
.neon-title {
  font-size: clamp(2.2rem, 5vw, 4rem);
  letter-spacing: 0.02em;
  margin: 0;
  text-align: center;
  color: var(--neon-cyan);

  /* Core glow using layered text shadows */
  text-shadow:
    0 0 1px #fff,                               /* hot core glint */
    0 0 var(--glow-weak) var(--neon-cyan),      /* inner cyan glow */
    0 0 var(--glow-med) var(--neon-cyan),       /* middle cyan bloom */
    0 0 var(--glow-strong) var(--neon-magenta); /* color fringe for richness */
}

/* Optional outline via pseudo-element for extra bloom separation */
.neon-title::after {
  content: "";
  position: absolute;
  /* Create a stacking context target */
}

.demo {
  position: relative; /* allows .neon-title::after to position if needed */
}

How This Works (Code Breakdown)

The color on the element sets the visible “tube” color. The first 1px white text-shadow simulates the hottest portion of a neon tube. That tight highlight makes edges feel crisp. Two cyan shadows at var(–glow-weak) and var(–glow-med) add a soft halo close to the letters, which boosts readability against the dark background. The magenta shadow at var(–glow-strong) sits further out and adds a slight color separation that reads like a secondary bloom. Mixing hues like this is a simple way to give neon depth without images.

The font size uses clamp to scale across viewports while holding a sensible min and max. Light spacing on letters keeps the bloom from merging into a single blob. The title stays clearly readable while still glowing.

If you want a clean geometric shape to glow behind the title, you can layer a separate element and shape it as a circle with border-radius: 50%. If you have not built a circle before, see how to make a circle with CSS and then apply the glow stack to that element.

Step 4: Building the Circular Neon Badge

The badge is a circle with a soft radial fill, an outer bloom, and glowing text inside. Pseudo-elements create a ring and a faint glass reflection. This structure mirrors an actual neon sign: a bright tube, a soft aura, and a subtle surface shine.

/* CSS */
.neon-badge {
  --size: 168px;
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  position: relative;

  /* Subtle base so the text glow has context */
  background: radial-gradient(circle at 50% 55%, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0.02) 35%, transparent 60%);

  /* Outer bloom */
  box-shadow:
    0 0 var(--glow-weak) var(--neon-magenta),
    0 0 var(--glow-med) var(--neon-magenta),
    0 0 var(--glow-strong) rgba(255, 0, 255, 0.35);
  display: grid;
  place-items: center;
  isolation: isolate; /* keeps internal filters from bleeding out */
}

.neon-badge span {
  font-weight: 700;
  font-size: 1.5rem;
  letter-spacing: 0.35em;
  color: var(--neon-green);
  text-shadow:
    0 0 1px #fff,
    0 0 var(--glow-weak) var(--neon-green),
    0 0 var(--glow-med) var(--neon-green),
    0 0 var(--glow-strong) rgba(57, 255, 20, 0.5);
}

/* Outer ring for definition */
.neon-badge::before {
  content: "";
  position: absolute;
  inset: 8px;
  border-radius: inherit;
  border: 2px solid rgba(255, 0, 255, 0.45);
  box-shadow:
    0 0 var(--glow-weak) var(--neon-magenta),
    inset 0 0 var(--glow-weak) rgba(255, 0, 255, 0.5);
}

/* Glassy reflection */
.neon-badge::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background:
    radial-gradient(60% 30% at 60% 30%, rgba(255,255,255,0.12), transparent 60%),
    linear-gradient(to bottom, rgba(255,255,255,0.05), transparent 45%);
  mix-blend-mode: screen;
  pointer-events: none;
}

How This Works (Code Breakdown)

The circular badge uses a fixed size with border-radius: 50% to achieve the round shape. The radial background adds a faint hotspot; this gives your eye a sense of depth before the glow layers even come in. Box-shadow on the base element establishes the outer bloom, and the ::before ring sharpens the edge so the badge does not blur into the page.

The inner text has its own text-shadow stack to read like a separate neon tube inside the sign. Going with green for the text adds color contrast against the magenta bloom. Layering distinct hues reads well and adds a hint of chromatic aberration, which is a nice touch for neon.

If you would rather build the badge from a triangle marker or a different shape, you can still apply the same glow recipe. For a button with a right-pointing triangle, see how to make a triangle right with CSS, then add the glow stack to that element.

Advanced Techniques: Adding Animations & Hover Effects

Real neon signs do not sit perfectly still. They hum, pulse, and sometimes flicker. You can mimic that behavior with light touch animations. The title will pulse softly, the badge ring will flicker once in a while, and the button will intensify on hover.

/* CSS */
.neon-title {
  animation: pulseGlow 3.2s ease-in-out infinite;
}

@keyframes pulseGlow {
  0%, 100% {
    text-shadow:
      0 0 1px #fff,
      0 0 var(--glow-weak) var(--neon-cyan),
      0 0 var(--glow-med) var(--neon-cyan),
      0 0 var(--glow-strong) var(--neon-magenta);
  }
  50% {
    text-shadow:
      0 0 2px #fff,
      0 0 calc(var(--glow-weak) + 2px) var(--neon-cyan),
      0 0 calc(var(--glow-med) + 4px) var(--neon-cyan),
      0 0 calc(var(--glow-strong) + 6px) var(--neon-magenta);
  }
}

/* Button with neon glow and arrow */
.neon-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.75rem 1rem 0.75rem 1.1rem;
  color: var(--neon-cyan);
  text-decoration: none;
  border: 2px solid rgba(0, 229, 255, 0.65);
  border-radius: 999px;
  position: relative;
  letter-spacing: 0.02em;
  transition: transform 0.2s, box-shadow 0.2s, color 0.2s, border-color 0.2s;
  text-shadow:
    0 0 1px #fff,
    0 0 var(--glow-weak) var(--neon-cyan);
  box-shadow:
    inset 0 0 var(--glow-weak) rgba(0, 229, 255, 0.65),
    0 0 var(--glow-weak) rgba(0, 229, 255, 0.75);
}

/* Triangle arrow as a glowing indicator */
.neon-btn::after {
  content: "";
  width: 0;
  height: 0;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-left: 10px solid var(--neon-cyan);
  filter: drop-shadow(0 0 6px rgba(0, 229, 255, 0.8)) drop-shadow(0 0 12px rgba(0, 229, 255, 0.45));
}

.neon-btn:hover {
  transform: translateY(-1px);
  color: #b8fbff;
  border-color: rgba(184, 251, 255, 0.9);
  box-shadow:
    inset 0 0 var(--glow-med) rgba(0, 229, 255, 0.8),
    0 0 var(--glow-med) rgba(0, 229, 255, 0.85),
    0 0 var(--glow-strong) rgba(0, 229, 255, 0.45);
}

/* Flicker on the badge ring */
@keyframes flicker {
  0%, 19%, 21%, 23%, 100% {
    opacity: 1;
    filter: none;
  }
  20%, 22% {
    opacity: 0.7;
    filter: brightness(1.1);
  }
  55% {
    opacity: 0.85;
  }
}

.neon-badge::before {
  animation: flicker 6s linear infinite;
}

/* Respect user motion preference */
@media (prefers-reduced-motion: reduce) {
  .neon-title,
  .neon-badge::before {
    animation: none;
  }
  .neon-btn {
    transition: none;
  }
}

The pulseGlow keyframes scale the intensity of the shadows rather than switching colors. That keeps the movement subtle and readable. The button uses transitions so the glow ramps up on hover and the element lifts slightly. The arrow uses the border triangle trick paired with filter: drop-shadow to bloom the point. The badge ring flickers with quick dips in opacity near the start of the cycle, which feels like an irregular voltage twitch. The prefers-reduced-motion query shuts animations off for users who request less movement.

Accessibility & Performance

Neon does not need to be hard to read or heavy to render. A few guardrails will keep it sharp and friendly.

Accessibility

Keep contrast by balancing neon foreground color with a dark background. The bright inner shadows should not overwhelm the letterforms. Check perceived contrast at the base layer because text-shadow does not always increase real contrast. For decorative effects, you can mark them as presentational. The badge uses a label on the container and a standard span for the text, which keeps the content available to assistive tech. If the badge is purely decorative in your UI, use aria-hidden=”true” and drop the label.

Motion should feel gentle. Long-running breathing animations are fine if they avoid sudden jumps. The prefers-reduced-motion query above disables the pulse and flicker, which keeps the interface steady for sensitive users. Do not tie key content visibility to animation progress.

Performance

Text-shadow and box-shadow are fast for static rendering, but large blurred shadows can be costly when animated. Favor animating opacity and subtle shadow radius changes over large blur radius shifts at high frequency. Short glow stacks look as good as very long ones once the radii get large. Use isolation on components like the badge to keep blending work local. Avoid animating expensive filters across big elements; drop-shadow on a small arrow is fine, but a full-screen filter will trigger heavier repaint work.

Keep Your Glow Under Control

You built a reusable neon system: layered text glow for headings, a circular badge with ring and reflection, and a button that blooms on hover. You now have a set of patterns you can swap onto any element or shape. Keep your glow stacks tidy with variables, and experiment with hue pairs to match your brand. Your next UI can light up with custom neon signatures built from plain CSS.

Leave a Comment