30 CSS Text Hover Effects07 / 30

CSS onlyMIT licensed

Neon Glow / Flicker Text Effect

A bar sign that ignites on hover: five stacked text-shadows build the tube glow (white-hot core, tight halo, wide bloom), a steps(1) keyframe sputters through the power-on like real gas catching, and one letter — the W, obviously — stays faulty, buzzing on its own clock even after ignition. Off-state isn't invisible: the tubes read as dim glass, the honest resting state of a real sign. Everything is color + text-shadow, so the markup is one anchor and the words stay words.

Published

Live Demo
Try it

The code

<section class="cth-07" aria-label="Neon flicker text demo">
  <div class="cth-07__stage">
    <div class="cth-07__scene">
      <a class="cth-07__sign" href="#cth07-top" id="cth07-top" aria-label="Night Owl — cocktails till 3 a.m.">
        <span class="cth-07__word" aria-hidden="true">NIGHT</span>
        <span class="cth-07__word cth-07__word--pink" aria-hidden="true">O<i class="cth-07__faulty">W</i>L</span>
        <span class="cth-07__pill" aria-hidden="true">cocktails &middot; til 3 a.m.</span>
      </a>
      <p class="cth-07__hint">Hover the sign and hold &mdash; ignition is a <code>steps(1)</code> sputter, the glow is five stacked <code>text-shadow</code> layers, and the W was always going to be the problem.</p>
    </div>
  </div>
</section>
.cth-07,
.cth-07 *,
.cth-07 *::before,
.cth-07 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cth-07 {
  --wall: oklch(0.16 0.012 310);
  --tube: oklch(0.83 0.14 195);
  --tube2: oklch(0.73 0.21 350);
  --off: oklch(0.42 0.03 220);
  --off2: oklch(0.42 0.06 350);
  --mut: oklch(0.6 0.02 300);
  font-family: ui-rounded,'SF Pro Rounded','Segoe UI',system-ui,sans-serif;
  color: oklch(0.93 0.01 300);
}

.cth-07__stage {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  background: radial-gradient(80% 60% at 50% 38%,oklch(0.2 0.02 310),var(--wall) 72%);
  padding: clamp(20px,4vw,56px);
  container: cth07/inline-size;
}

.cth-07__scene {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(22px,4cqi,34px);
  text-align: center;
}

.cth-07__sign {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(8px,1.6cqi,14px);
  text-decoration: none;
  padding: clamp(20px,4cqi,36px) clamp(26px,5cqi,52px);
  border-radius: 22px;
}

.cth-07__sign:focus-visible {
  outline: 2px solid var(--tube);
  outline-offset: 6px;
}

.cth-07__word {
  display: block;
  font-size: clamp(52px,12cqi,120px);
  font-weight: 800;
  letter-spacing: .06em;
  line-height: .95;
  color: var(--off);
  text-shadow: 0 0 .1em color-mix(in oklch,var(--tube) 18%,transparent);
  transition: color .45s ease,text-shadow .45s ease;
}

.cth-07__word--pink {
  color: var(--off2);
  text-shadow: 0 0 .1em color-mix(in oklch,var(--tube2) 18%,transparent);
}

.cth-07__sign:hover .cth-07__word,
.cth-07__sign:focus-visible .cth-07__word {
  color: oklch(0.97 0.03 195);
  text-shadow: 0 0 .03em oklch(1 0 0/.9),0 0 .12em var(--tube),0 0 .35em var(--tube),0 0 .85em color-mix(in oklch,var(--tube) 65%,transparent),0 0 1.6em color-mix(in oklch,var(--tube) 40%,transparent);
  animation: cth07-ignite .95s steps(1,end);
}

.cth-07__sign:hover .cth-07__word--pink,
.cth-07__sign:focus-visible .cth-07__word--pink {
  color: oklch(0.94 0.06 350);
  text-shadow: 0 0 .03em oklch(1 0 0/.9),0 0 .12em var(--tube2),0 0 .35em var(--tube2),0 0 .85em color-mix(in oklch,var(--tube2) 65%,transparent),0 0 1.6em color-mix(in oklch,var(--tube2) 40%,transparent);
  animation: cth07-ignite 1.15s steps(1,end);
}

@keyframes cth07-ignite {
  0%,
    9%,
    24%,
    56% {
    color: inherit;
    text-shadow: none;
    opacity: .4;
  }

  5%,
    12%,
    28%,
    54%,
    60%,
    100% {
    opacity: 1;
  }
}

.cth-07__faulty {
  font-style: normal;
  animation: cth07-buzz-off 5s steps(1,end) infinite;
}

.cth-07__sign:hover .cth-07__faulty,
.cth-07__sign:focus-visible .cth-07__faulty {
  animation: cth07-buzz-on 3.2s steps(1,end) infinite;
}

@keyframes cth07-buzz-off {
  0%,
    100% {
    opacity: 1;
  }

  7%,
    9.5%,
    42% {
    opacity: .55;
  }

  8%,
    43.5%,
    71% {
    opacity: 1;
  }

  70% {
    opacity: .7;
  }
}

@keyframes cth07-buzz-on {
  0%,
    100% {
    opacity: 1;
  }

  11%,
    13%,
    57%,
    58.6% {
    opacity: .5;
  }

  12%,
    14%,
    58%,
    60% {
    opacity: 1;
  }
}

.cth-07__pill {
  margin-top: 6px;
  font-size: clamp(13px,2cqi,17px);
  font-weight: 700;
  letter-spacing: .22em;
  text-transform: lowercase;
  color: var(--off2);
  border: 2px solid color-mix(in oklch,var(--off2) 60%,transparent);
  border-radius: 99px;
  padding: 10px 22px;
  transition: color .5s ease,border-color .5s ease,box-shadow .5s ease,text-shadow .5s ease;
}

.cth-07__sign:hover .cth-07__pill,
.cth-07__sign:focus-visible .cth-07__pill {
  color: oklch(0.94 0.06 350);
  border-color: var(--tube2);
  text-shadow: 0 0 .3em var(--tube2);
  box-shadow: 0 0 .6em color-mix(in oklch,var(--tube2) 55%,transparent),inset 0 0 .5em color-mix(in oklch,var(--tube2) 35%,transparent);
  transition-delay: .18s;
}

.cth-07__hint {
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--mut);
  max-width: 52ch;
}

.cth-07__hint code {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: .92em;
  background: oklch(0.24 0.015 310);
  padding: 1px 6px;
  border-radius: 4px;
  color: var(--tube);
}

@media (prefers-reduced-motion: reduce) {
  .cth-07 * {
    animation: none !important;
    transition-duration: .25s !important;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Text Hover Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Neon Glow / Flicker Text Effect
Source: https://codefronts.com/motion/css-text-hover-effects/neon-glow-flicker-text-effect/

A bar sign that ignites on hover: five stacked text-shadows build the tube glow (white-hot core, tight halo, wide bloom), a steps(1) keyframe sputters through the power-on like real gas catching, and one letter — the W, obviously — stays faulty, buzzing on its own clock even after ignition. Off-state isn't invisible: the tubes read as dim glass, the honest resting state of a real sign. Everything is color + text-shadow, so the markup is one anchor and the words stay words.
## HTML
```html
<section class="cth-07" aria-label="Neon flicker text demo">
  <div class="cth-07__stage">
    <div class="cth-07__scene">
      <a class="cth-07__sign" href="#cth07-top" id="cth07-top" aria-label="Night Owl — cocktails till 3 a.m.">
        <span class="cth-07__word" aria-hidden="true">NIGHT</span>
        <span class="cth-07__word cth-07__word--pink" aria-hidden="true">O<i class="cth-07__faulty">W</i>L</span>
        <span class="cth-07__pill" aria-hidden="true">cocktails &middot; til 3 a.m.</span>
      </a>
      <p class="cth-07__hint">Hover the sign and hold &mdash; ignition is a <code>steps(1)</code> sputter, the glow is five stacked <code>text-shadow</code> layers, and the W was always going to be the problem.</p>
    </div>
  </div>
</section>
```
## CSS
```css
.cth-07,
.cth-07 *,
.cth-07 *::before,
.cth-07 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cth-07 {
  --wall: oklch(0.16 0.012 310);
  --tube: oklch(0.83 0.14 195);
  --tube2: oklch(0.73 0.21 350);
  --off: oklch(0.42 0.03 220);
  --off2: oklch(0.42 0.06 350);
  --mut: oklch(0.6 0.02 300);
  font-family: ui-rounded,'SF Pro Rounded','Segoe UI',system-ui,sans-serif;
  color: oklch(0.93 0.01 300);
}

.cth-07__stage {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  background: radial-gradient(80% 60% at 50% 38%,oklch(0.2 0.02 310),var(--wall) 72%);
  padding: clamp(20px,4vw,56px);
  container: cth07/inline-size;
}

.cth-07__scene {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(22px,4cqi,34px);
  text-align: center;
}

.cth-07__sign {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(8px,1.6cqi,14px);
  text-decoration: none;
  padding: clamp(20px,4cqi,36px) clamp(26px,5cqi,52px);
  border-radius: 22px;
}

.cth-07__sign:focus-visible {
  outline: 2px solid var(--tube);
  outline-offset: 6px;
}

.cth-07__word {
  display: block;
  font-size: clamp(52px,12cqi,120px);
  font-weight: 800;
  letter-spacing: .06em;
  line-height: .95;
  color: var(--off);
  text-shadow: 0 0 .1em color-mix(in oklch,var(--tube) 18%,transparent);
  transition: color .45s ease,text-shadow .45s ease;
}

.cth-07__word--pink {
  color: var(--off2);
  text-shadow: 0 0 .1em color-mix(in oklch,var(--tube2) 18%,transparent);
}

.cth-07__sign:hover .cth-07__word,
.cth-07__sign:focus-visible .cth-07__word {
  color: oklch(0.97 0.03 195);
  text-shadow: 0 0 .03em oklch(1 0 0/.9),0 0 .12em var(--tube),0 0 .35em var(--tube),0 0 .85em color-mix(in oklch,var(--tube) 65%,transparent),0 0 1.6em color-mix(in oklch,var(--tube) 40%,transparent);
  animation: cth07-ignite .95s steps(1,end);
}

.cth-07__sign:hover .cth-07__word--pink,
.cth-07__sign:focus-visible .cth-07__word--pink {
  color: oklch(0.94 0.06 350);
  text-shadow: 0 0 .03em oklch(1 0 0/.9),0 0 .12em var(--tube2),0 0 .35em var(--tube2),0 0 .85em color-mix(in oklch,var(--tube2) 65%,transparent),0 0 1.6em color-mix(in oklch,var(--tube2) 40%,transparent);
  animation: cth07-ignite 1.15s steps(1,end);
}

@keyframes cth07-ignite {
  0%,
    9%,
    24%,
    56% {
    color: inherit;
    text-shadow: none;
    opacity: .4;
  }

  5%,
    12%,
    28%,
    54%,
    60%,
    100% {
    opacity: 1;
  }
}

.cth-07__faulty {
  font-style: normal;
  animation: cth07-buzz-off 5s steps(1,end) infinite;
}

.cth-07__sign:hover .cth-07__faulty,
.cth-07__sign:focus-visible .cth-07__faulty {
  animation: cth07-buzz-on 3.2s steps(1,end) infinite;
}

@keyframes cth07-buzz-off {
  0%,
    100% {
    opacity: 1;
  }

  7%,
    9.5%,
    42% {
    opacity: .55;
  }

  8%,
    43.5%,
    71% {
    opacity: 1;
  }

  70% {
    opacity: .7;
  }
}

@keyframes cth07-buzz-on {
  0%,
    100% {
    opacity: 1;
  }

  11%,
    13%,
    57%,
    58.6% {
    opacity: .5;
  }

  12%,
    14%,
    58%,
    60% {
    opacity: 1;
  }
}

.cth-07__pill {
  margin-top: 6px;
  font-size: clamp(13px,2cqi,17px);
  font-weight: 700;
  letter-spacing: .22em;
  text-transform: lowercase;
  color: var(--off2);
  border: 2px solid color-mix(in oklch,var(--off2) 60%,transparent);
  border-radius: 99px;
  padding: 10px 22px;
  transition: color .5s ease,border-color .5s ease,box-shadow .5s ease,text-shadow .5s ease;
}

.cth-07__sign:hover .cth-07__pill,
.cth-07__sign:focus-visible .cth-07__pill {
  color: oklch(0.94 0.06 350);
  border-color: var(--tube2);
  text-shadow: 0 0 .3em var(--tube2);
  box-shadow: 0 0 .6em color-mix(in oklch,var(--tube2) 55%,transparent),inset 0 0 .5em color-mix(in oklch,var(--tube2) 35%,transparent);
  transition-delay: .18s;
}

.cth-07__hint {
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--mut);
  max-width: 52ch;
}

.cth-07__hint code {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: .92em;
  background: oklch(0.24 0.015 310);
  padding: 1px 6px;
  border-radius: 4px;
  color: var(--tube);
}

@media (prefers-reduced-motion: reduce) {
  .cth-07 * {
    animation: none !important;
    transition-duration: .25s !important;
  }
}
```

How this works

Neon is a lighting model you can fake with shadow stacking — each layer is one component of how a tube actually looks:

.sign:hover .word{
  color:oklch(0.97 0.03 195);        /* white-hot core       */
  text-shadow:
    0 0 .03em oklch(1 0 0 / .9),     /* filament             */
    0 0 .12em var(--tube),           /* glass glow           */
    0 0 .35em var(--tube),           /* near halo            */
    0 0 .85em color-mix(in oklch,var(--tube) 65%,transparent),
    0 0 1.6em color-mix(in oklch,var(--tube) 40%,transparent); }

All radii are in em, so the bloom scales with the headline instead of needing retuning per breakpoint. The ignition is a steps(1,end) animation that toggles between the off and on states at irregular timestamps (0, 9, 12, 24, 28, 54, 57%…) — steps() means no crossfade between sputters, which is exactly how discharge tubes behave; the keyframe ends at 100% lit with forwards, and mouse-out falls back to the plain color/text-shadow transition for a smooth power-down.

The character in the design is the broken letter: one span runs a slow, permanent steps(1) buzz that dips its opacity at odd moments — at rest AND after ignition (where it sputters between 55% and full). One faulty tube makes the whole sign believable; it's also the piece to delete first if the client finds it too honest. The words are wrapped span-per-word with aria-hidden, and the anchor carries an aria-label — screen readers get 'Night Owl, cocktails till 3am', not letter soup.

Make it yours

  • Tube color is one token: --tube in oklch — 195 (cyan) and 350 (pink) are the classic pairing here; 145 phosphor green reads arcade, 55 amber reads whiskey bar.
  • Ignition tempo: stretch the animation to 2.5s and add two more dark frames for a tired sign; cut to .5s with one sputter for a healthy one.
  • Retire the faulty letter by deleting its animation — or move the class to a different span; art-directing WHICH letter is broken is half the fun.
  • Layer a hum: box-shadow with the same color stack on a rounded border turns any container into the sign's tube frame (the 'til 3 a.m.' pill downstairs does this).

Gotchas — read before shipping

  • text-shadow ignores spread (unlike box-shadow) — you widen the glow by stacking MORE layers at bigger radii, not by adding a fourth length value (it's invalid and kills the whole declaration).
  • Five shadow layers × long text = real paint cost. Fine for a sign or hero word; don't neon an entire nav.
  • Contrast discipline: the OFF state must still pass contrast against the wall (dim ≠ illegible) — this off-state sits near 4.6:1; check yours.
  • Flicker is seizure-adjacent territory: keep dips ≥ 60ms apart, never strobe below that, and hard-disable both flicker animations under prefers-reduced-motion (this demo swaps to a plain crossfade).

Browser support

ChromeSafariFirefoxEdge
111+16.4+113+111+

text-shadow stacking and steps() run in any 2015+ engine; the floor is oklch()/color-mix() (swap hex/rgba to go older). ui-rounded falls back to system-ui outside Apple platforms — by design.

Techniques used in this demo

Search CodeFronts

Loading…