← Blog8 min read#neon#css animation#glow

CSS Neon Animation: Flickering Lights, Pulse and Glow Sequences

Build realistic CSS neon sign animations - flickering lights, glowing pulse loops, and multi-color sequences - with pure CSS keyframes and no JavaScript.

neon pink and blue glowing light tubes on a dark wall

Why Neon Animation Hits Differently

There's a reason neon sign aesthetics never fully die. The flicker, the warm hum, the way the glow bleeds into surrounding darkness - it's viscerally satisfying in a way that flat color never is. And in 2026, CSS is absolutely capable of reproducing every nuance of that look without a single line of JavaScript or a heavy canvas renderer.

The secret is stacking box-shadow and text-shadow values. A real neon tube doesn't emit one clean edge of light - it radiates outward in concentric halos, each dimmer than the last. You replicate that with multiple shadow layers at increasing blur radii. Honestly, once you see the layered shadow technique, you'll wonder why you ever reached for a CSS animation library for this.

That said, neon animation goes beyond a static glow. The magic is in the *movement* - the flicker that feels accidental, the slow pulse that breathes, the sequence where multiple signs light up one by one. All of that is pure @keyframes work. Let's build every variant.

Worth noting: if you want ready-made cyberpunk and neon-styled UI components without writing all this from scratch, Empire UI's cyberpunk collection ships with glow-animated buttons, borders, and text effects you can copy directly.

The Core Neon Glow: Text and Box Shadows

Before anything animates, you need a solid static glow. The technique uses text-shadow for text-based neon signs and box-shadow for bordered elements like tubes and buttons. You layer at least four shadow values - a tight 2px core in near-white, then progressively wider radii at the full neon color.

/* Static neon glow - hot pink */
.neon-text {
  color: #fff;
  text-shadow:
    0 0 2px #fff,
    0 0 8px #ff2d78,
    0 0 20px #ff2d78,
    0 0 40px #ff2d78,
    0 0 80px #c0004a;
  font-family: 'Courier New', monospace;
  font-size: 64px;
  letter-spacing: 0.05em;
}

/* Neon border tube */
.neon-box {
  border: 2px solid #ff2d78;
  border-radius: 8px;
  box-shadow:
    0 0 4px #ff2d78,
    0 0 12px #ff2d78,
    0 0 30px #ff2d78,
    inset 0 0 8px rgba(255,45,120,0.3);
}

The inset shadow on .neon-box is a small detail that makes a big visual difference - it simulates light bouncing off the inner face of the tube. Skip it and the box looks flat. Keep it and it suddenly feels three-dimensional.

One more thing - background color matters enormously. Neon glows only read against dark backgrounds. Use #0a0a0a or a dark navy rather than pure #000000. Pure black kills the ambient diffusion that makes neon look physical. A value around #0d0d1a gives you just enough surface for the glow to land on.

Quick aside: the gradient generator is useful here - you can build a dark-to-slightly-less-dark background gradient that gives your neon signs visual breathing room without blowing out the effect.

Flickering Neon: Randomness via Keyframes

A perfect, steady glow looks fake. Real neon flickers - tubes have gas discharge irregularities, worn electrodes, loose connections. You replicate that "broken" quality by defining irregular opacity and shadow intensity jumps across non-uniform keyframe percentages.

@keyframes neon-flicker {
  0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% {
    text-shadow:
      0 0 2px #fff,
      0 0 8px #ff2d78,
      0 0 20px #ff2d78,
      0 0 40px #ff2d78;
    opacity: 1;
  }
  20%, 24%, 55% {
    text-shadow: none;
    opacity: 0.4;
  }
}

.neon-flicker {
  animation: neon-flicker 3s infinite;
}

The key is clustering two or three rapid none states close together - 20%, 24% - then going stable for a long stretch before another burst. That's what makes it read as an electrical glitch rather than a regular blink. If you space the drops evenly, it looks like a cursor; clustered, it looks like a dying sign.

In practice, I'll often add a second, slower animation on the same element with animation-delay to shift phase between two elements. Two signs on the same page with identical flicker cycles look choreographed and fake. Offset the second by 1.3s and they feel independent.

.neon-flicker--delayed {
  animation: neon-flicker 3s 1.3s infinite;
}

Pulse and Breathe: Smooth Glow Loops

Flickering is the aggressive option. Sometimes you want something that breathes - a slow, hypnotic pulse where the glow swells and contracts. Think ambient signage, loading indicators, or the border of an active input field. This is where ease-in-out sinusoidal timing earns its keep.

@keyframes neon-pulse {
  0%, 100% {
    text-shadow:
      0 0 4px #fff,
      0 0 10px #00f9ff,
      0 0 25px #00f9ff,
      0 0 50px #00f9ff;
    opacity: 1;
  }
  50% {
    text-shadow:
      0 0 2px #fff,
      0 0 6px #00a0a8,
      0 0 14px #00a0a8;
    opacity: 0.75;
  }
}

.neon-pulse {
  animation: neon-pulse 2.4s ease-in-out infinite;
}

That 2.4s duration hits a sweet spot - slow enough to feel like breathing, fast enough to stay dynamic. Drop below 1.5s and it starts to feel anxious. Push above 4s and users miss the loop entirely on first glance. Look, this is one of those cases where the exact millisecond matters, so test it on a real screen rather than eyeballing it in DevTools.

You can also animate filter: brightness() instead of re-declaring shadows, which is cheaper on the GPU in some rendering pipelines. It's worth benchmarking on your target device - mobile Safari in particular handles filter animations more gracefully than deeply stacked box-shadow recalculations.

@keyframes neon-brightness-pulse {
  0%, 100% { filter: brightness(1) drop-shadow(0 0 12px #00f9ff); }
  50% { filter: brightness(0.6) drop-shadow(0 0 4px #00a0a8); }
}

The drop-shadow() filter function (not box-shadow) follows the actual shape of the element including any transparent cutouts - handy for SVG neon logos where you want the glow to follow a custom path rather than the bounding rectangle.

Multi-Color Neon Sequences

Here's where it gets fun. Real neon districts - Tokyo's Shinjuku, Las Vegas in the 1980s, any vaporwave aesthetic - feature signs that cycle through colors or light up in sequence. You can fake both with pure CSS.

Color cycling on a single element uses keyframe stops to shift the text-shadow hue across the spectrum. The trick is transitioning through white-ish midpoints so the color change looks like the gas tube recharging, not a hard swap.

@keyframes neon-color-cycle {
  0%   { text-shadow: 0 0 8px #fff, 0 0 30px #ff2d78, 0 0 60px #ff2d78; }
  25%  { text-shadow: 0 0 8px #fff, 0 0 30px #a855f7, 0 0 60px #a855f7; }
  50%  { text-shadow: 0 0 8px #fff, 0 0 30px #00f9ff, 0 0 60px #00f9ff; }
  75%  { text-shadow: 0 0 8px #fff, 0 0 30px #22c55e, 0 0 60px #22c55e; }
  100% { text-shadow: 0 0 8px #fff, 0 0 30px #ff2d78, 0 0 60px #ff2d78; }
}

.neon-rainbow {
  color: #fff;
  animation: neon-color-cycle 6s linear infinite;
}

For a sequential light-up effect across multiple signs, stagger animation-delay on sibling elements. Give each one the same @keyframes that goes from opacity: 0 with no shadow to full glow, but delay them by 0.4s increments. It reads as one sign turning on after another.

.sign-row .sign { animation: neon-on 0.5s ease-out forwards; }
.sign-row .sign:nth-child(1) { animation-delay: 0s; }
.sign-row .sign:nth-child(2) { animation-delay: 0.4s; }
.sign-row .sign:nth-child(3) { animation-delay: 0.8s; }
.sign-row .sign:nth-child(4) { animation-delay: 1.2s; }

@keyframes neon-on {
  0%   { opacity: 0; text-shadow: none; }
  60%  { opacity: 1; text-shadow: 0 0 2px #fff, 0 0 6px #ff2d78; }
  80%  { opacity: 0.7; }
  100% { opacity: 1; text-shadow: 0 0 2px #fff, 0 0 8px #ff2d78, 0 0 30px #ff2d78; }

That dip at 80% opacity simulates the tube sputtering before it stabilizes - another one of those small touches that makes the whole thing feel mechanical rather than digital. The cyberpunk components on Empire UI use a similar warm-up sequence on their button borders.

Performance, Accessibility, and `prefers-reduced-motion`

CSS animations that loop infinitely and recalculate complex shadow stacks every frame are not free. On mobile, stacked text-shadow animations can drop frames, especially when multiple elements animate simultaneously. The fix: add will-change: text-shadow, opacity to elements you know will animate. This hoists them onto their own compositing layer before the animation starts.

.neon-text {
  will-change: text-shadow, opacity;
  transform: translateZ(0); /* force GPU layer */
}

The transform: translateZ(0) trick still works in 2026 across all major engines as a layer-promotion hint - it's not deprecated, just not the only way anymore. Use it when will-change alone doesn't produce the compositing behavior you expect in Safari.

On the accessibility front: users with vestibular disorders or photosensitive epilepsy can have real issues with rapid flickering. The 20%/24% clustering in the flicker animation above is aggressive - always gate it behind a prefers-reduced-motion media query. Here's the correct pattern:

@media (prefers-reduced-motion: no-preference) {
  .neon-flicker {
    animation: neon-flicker 3s infinite;
  }
  .neon-pulse {
    animation: neon-pulse 2.4s ease-in-out infinite;
  }
}

/* Static glow for users who prefer reduced motion */
.neon-flicker,
.neon-pulse {
  text-shadow:
    0 0 2px #fff,
    0 0 8px #ff2d78,
    0 0 20px #ff2d78;
}

This way the neon look is preserved for everyone - only the animation is conditionally removed. It's a much better UX than hiding the element entirely or stripping the visual effect.

Putting It All Together in a React Component

Here's a composable React component that bundles the static glow, the flicker variant, and the pulse variant behind a single variant prop. It uses CSS custom properties for the color so you can theme it from the parent without touching the keyframe declarations.

// NeonText.tsx
import styles from './NeonText.module.css';

type NeonVariant = 'static' | 'flicker' | 'pulse';
type NeonColor = 'pink' | 'cyan' | 'green' | 'purple';

interface NeonTextProps {
  children: string;
  variant?: NeonVariant;
  color?: NeonColor;
  as?: 'h1' | 'h2' | 'h3' | 'p' | 'span';
}

const colorMap: Record<NeonColor, string> = {
  pink:   '#ff2d78',
  cyan:   '#00f9ff',
  green:  '#39ff14',
  purple: '#a855f7',
};

export function NeonText({
  children,
  variant = 'static',
  color = 'pink',
  as: Tag = 'span',
}: NeonTextProps) {
  return (
    <Tag
      className={`${styles.neon} ${styles[variant]}`}
      style={{ '--neon-color': colorMap[color] } as React.CSSProperties}
    >
      {children}
    </Tag>
  );
}
/* NeonText.module.css */
.neon {
  color: #fff;
  text-shadow:
    0 0 2px #fff,
    0 0 8px var(--neon-color),
    0 0 20px var(--neon-color),
    0 0 40px var(--neon-color);
  will-change: text-shadow, opacity;
}

@media (prefers-reduced-motion: no-preference) {
  .flicker { animation: neon-flicker 3s infinite; }
  .pulse   { animation: neon-pulse 2.4s ease-in-out infinite; }
}

/* keyframe blocks from earlier sections go here */

The CSS custom property approach means you can drop NeonText into a cyberpunk or vaporwave themed page and control the hue purely from the JSX. No prop drilling for individual shadow colors, no re-declaring keyframes per color. That's the kind of abstraction that actually ages well in a design system.

For more complex animated backgrounds to pair with your neon text, take a look at the shooting stars background article - the dark, particle-driven background works brilliantly behind neon signage and the combination ships in several of Empire UI's templates.

FAQ

Can I animate CSS neon glow without JavaScript?

Yes, entirely. CSS @keyframes with text-shadow and opacity changes handle all the flicker, pulse, and color-cycle effects described here. JavaScript isn't needed unless you want runtime-controlled timing or user-triggered sequences.

Why does my neon glow look washed out?

Almost always a background issue. Neon glows need a dark surface - use #0d0d1a or similar instead of white or mid-gray. Also check that you're layering at least four text-shadow values with increasing blur radii, not just a single drop-shadow.

Is a flickering neon animation accessible?

Rapid flicker can trigger photosensitive conditions, so you must wrap all flicker animations inside @media (prefers-reduced-motion: no-preference) and fall back to a static glow. The pulse variant is generally safer, but still benefits from the same media query guard.

How do I make neon glow follow an SVG path rather than a rectangle?

Use filter: drop-shadow() on the SVG element instead of box-shadow. Unlike box-shadow, drop-shadow() respects the actual alpha shape of the element and will hug the path contours of your SVG logo or icon.

Free components in 41 styles
React & Tailwind, copy-paste ready.
Browse β†’

Read next

CSS Animations & Motion Design: The Complete 2026 Playbook β†’Motion One: The Tiny Animation Library Punching Way Above Its Weight β†’Stagger Animation in CSS: animation-delay, nth-child and JS Approaches β†’Neon Text Effect in React: Animated Glow with CSS and Framer Motion β†’