Atmosphere✨ Premium

Neon Glow Bars

15 DOM bars in cyan / pink / purple pulse on a dual-sine audio simulation — 3-layer CSS neon glow (5–40 px) driven by a single rAF loop, no canvas.

CSSGlow

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Atmosphere has 57 effects, including 10 free. Explore the category →

3 usage examples

Hero / Landing — Welcome scene for an audio or gaming tool — Neon Glow Bars example 1

① Hero / Landing — Welcome scene for an audio or gaming tool

WhenHomepage of a music creation app, online DJ set, or real-time streaming dashboard.
WhyNeon EQ bars visually signal the audio nature of the product before the user reads a word — no sound needed.
Settings15 bars, gap: 6px, container height: 120px, baseFreq = 1.5 — default settings, rendered full-width as a hero.
Product component — Audio level widget in a player or recorder — Neon Glow Bars example 2

② Product component — Audio level widget in a player or recorder

WhenSignal indicator in a voice player, online recorder, or podcast monitoring screen.
WhyCompact footprint (10 px × 120 px per bar) fits a sidebar or footer. The dark/neon palette stays readable on dark backgrounds.
SettingsReduce to 8 bars (neonBarCount = 8), gap: 4px, container height: 80px — saves space without losing the visual effect.
Conversion micro-interaction — Subscription activation screen — Neon Glow Bars example 3

③ Conversion micro-interaction — Subscription activation screen

WhenVisual confirmation after a paid plan is activated or at the end of onboarding in a tech/audio app.
WhyThe cyan/pink/purple palette fits dark tech UIs naturally. The animation conveys product liveliness at the most decisive moment.
SettingsBoost the bloom: change const intensity = 0.8 + data[i] * 0.2 to keep the halo always on; set baseFreq = 2.0 for a faster, more dynamic rhythm.

How it works

Each bar is a <div> 10 px wide with border-radius: 5px 5px 0 0. The neon is rendered entirely in CSS: a triple-layer box-shadow using the same color — 10 px (inner halo), 20 px (diffusion), 40 px (wide bloom) — that JS rewrites every frame to make the intensity breathe with the signal. No canvas, no SVG.

The simulated signal comes from getSimulatedAudio(15, time, 1.5), which produces for each bar i the value |sin(t × freq × 0.002) × 0.5 + sin(t × freq × 0.003 + i) × 0.3 + rand × 0.2|, where freq = 1.5 + i × 0.5. Two sinusoids at different phase speeds per bar create a convincing EQ shape without an AudioContext; the 20 % random noise adds organic micro-variation.

The glow intensity is 0.5 + data[i] × 0.5 (range 0.5–1.0). Box-shadow radii are multiplied by this value: 0 0 ${10×intensity}px color, etc. When the bar is low, the halo dims to half; at peak, it reaches its full 40 px bloom. Bar height follows 10 + data[i] × 100 px (range 10–110 px) inside a 120 px container.

The CSS adds transition: height .05s to each bar, smoothing the jump between rAF frames without extra JS. The main loop — a single shared requestAnimationFrame — calls animateNeonBars(time) with the DOMHighResTimeStamp, keeping the animation framerate-stable.

Accessibility

  • prefers-reduced-motion absent: the code does not check this media query — the animation runs continuously even when the OS has reduced-motion enabled. Add manually for WCAG 2.1 SC 2.3.3 compliance: if (window.matchMedia('(prefers-reduced-motion:reduce)').matches) return; before the requestAnimationFrame call.
  • No aria-hidden on the .neon-bars container — screen readers traverse the bar elements with no useful announcement. Add aria-hidden="true" whenever the bars are purely decorative.
  • No role or aria-label on the scene. Depending on context, wrap in role="img" with a descriptive aria-label (e.g. 'Simulated audio visualizer').
  • The bars carry no text — no contrast issue on the animated elements themselves. Verify contrast of any overlay text added on top (WCAG AA ratio ≥ 4.5:1).
  • No keyboard interactivity on the bars — they do not capture focus or intercept keyboard events.

Browser compatibility

Requires CSS box-shadow, requestAnimationFrame, and DOM manipulation — supported in all modern browsers. No canvas or WebGL needed.

Chrome 100+✓ Full
Firefox 100+✓ Full
Safari 15+✓ Full
Edge 100+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

If JS is disabled, the initial HTML bars remain displayed statically with their inline styles. No errors, the page stays functional.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="neon-bars" id="neonBars" aria-hidden="true">
  <!-- Bars created by JS — 15 × cycling cyan / pink / purple -->
  <div class="bar cyan"></div>
  <div class="bar pink"></div>
  <div class="bar purple"></div>
  <!-- … 12 more bars … -->
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
CSS .neon-bars .bar.cyan { background: … } #06b6d4 Fill color and glow tint for cyan bars. Update the background and all 3 box-shadow color values (4 occurrences total).
CSS .neon-bars .bar.pink { background: … } #d946ef Pink bar color. Replace all 4 occurrences of #d946ef to retheme the full channel.
CSS .neon-bars .bar.purple { background: … } #8b5cf6 Purple bar color. Same — 4 occurrences in the CSS.
CSS .neon-bars { height: 120px } 120px Container height = ceiling of the tallest bar. Increase for a more dramatic effect, reduce for a discreet integration.
CSS .neon-bars { gap: 6px } 6px Horizontal spacing between bars. 2–3 px gives a dense clustered look; 10–12 px a more spread-out spectrum.
JS neonBarCount = 15 15 Number of bars created. Useful range: 8 (subtle) to 32 (dense spectrum). Change before DOM creation in the Audio Visualizer section.
JS getSimulatedAudio(…, 1.5) 1.5 3rd argument = base sine frequency. 0.5 = slow, undulating motion; 3.0 = fast, nervous energy. No effect on color.
JS const intensity = 0.5 + data[i] * 0.5 0.5 + data[i] * 0.5 Glow intensity formula. Use 0.8 + data[i] * 0.2 for always-on bright bloom; 0.2 + data[i] * 0.8 for high-contrast peak vs. trough.

FAQ

No — the simulation is 100% mathematical. getSimulatedAudio() overlays two sinusoids at different phase speeds per bar, plus 20% random noise. To connect a real FFT analyser, replace the returned values with data from an AnalyserNode.getByteFrequencyData() normalized to 0–1.
The rAF loop launches without exposing its id. To control it, capture the id at startup — replace requestAnimationFrame(animate) with const rafId = requestAnimationFrame(animate) — then call cancelAnimationFrame(rafId) when needed (component unmount, out-of-view, etc.).
Not directly — neonBarCount is read at init. To reconfigure: clear the container (neonBars.innerHTML = ''), update the constant, recreate bars with the init for loop. The already-running rAF loop will resume animating the new DOM without restart.