More Loaders Snippets
Signal Strength Bars — Free CSS Wifi Bars JS Snippet
Signal Strength Bars · Loaders · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Signal Strength Bars — Wifi / Cellular Level Meter

Signal strength bars are the stepped meter that shows wifi, cellular, or connection quality as a row of rising bars — the icon in every status bar and the indicator on dashboards, network tools, and IoT devices. This snippet builds a fully interactive one in HTML, CSS, and vanilla JavaScript: five graduated bars that light up to a level, a color and text label that follow strength, and a "scanning" animation that hunts before locking on. No images, no icon font, no dependency.
Graduated bars from CSS heights
The five bars are sibling <span>s whose heights step from 20% to 100% using :nth-child selectors, so they form the classic ascending staircase with markup that's just five empty spans. Each lit bar gets an .on class and reads its color from a CSS custom property (--sig), which means a single variable recolors the whole meter.
Strength drives color and label together
A LEVELS array maps each strength (0–5) to a name and color — "No signal" grey, "Poor" red, through "Excellent" green. The paint() function lights bars below the current level, sets --sig on each, and updates the label text and color from the same entry, so the visual and the words can never disagree. It also writes the label into aria-label so assistive tech announces "Signal strength: Good".
The scanning animation
Pressing scan adds a .scanning class that runs a staggered sigScan keyframe — each bar flashes blue with an increasing animation-delay, producing a left-to-right sweep like a device searching for signal. The slider is disabled during the scan to lock the UI, and a setTimeout "locks on" after ~2.2s by clearing the animation and snapping to a random realistic strength (2–5).
State you can't desync
Everything renders through paint() from one source of truth (the slider value), and the scan path resets the same state when it finishes. There's no duplicated "which bars are lit" logic, so the bars, color, label, and accessibility text always reflect the same number.
Wiring to real connectivity
Map a real metric — RSSI in dBm, the Network Information API's effectiveType, or a ping latency bucket — onto 0–5 and call paint(). Because rendering is decoupled from the source, you can poll, subscribe to navigator.connection.onchange, or stream device telemetry without touching the view code.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to work out the level-to-color mapping or the scan timing by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the LEVELS array drives the bars, the label, and the aria-label from one source of truth in paint(), or why the --sig custom property is set per bar instead of on the container. The same assistant is useful for optimizing it too, for instance checking whether the staggered animation-delay values on the scanning keyframe scale cleanly if you add more bars. It is just as good for extending the meter: ask it to wire paint() up to the real Network Information API's effectiveType, add a numeric dBm readout beside the label, or animate the lock-on result with a bounce instead of an instant snap. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a wifi-style "signal strength bars" meter in plain HTML, CSS, and JavaScript — no canvas, no SVG, no libraries.
Requirements:
- Five sibling span elements inside a flex container aligned to the bottom, with heights stepped via nth-child selectors (20%, 40%, 60%, 80%, 100%) so they form an ascending staircase using only CSS heights.
- A single array mapping strength levels 0 through 5 to a label name and a color. A paint() function driven by a range input must, from that one array entry, toggle an "on" class on every bar below the current level, set a shared CSS custom property used for the lit bar color, update a text label, and update the container's aria-label so assistive tech announces the strength as text — all four outputs must come from the same lookup so they can never disagree.
- A "simulate scanning" button that adds a scanning class which runs a CSS keyframe animation on every bar, with each bar's animation-delay staggered slightly more than the previous one, producing a left-to-right sweeping effect while scanning is active.
- While scanning, disable the range input so the user cannot change the level mid-scan. After roughly two seconds, stop the scanning animation automatically, snap the range input to a randomly chosen realistic strength value, and re-run the same paint() function used everywhere else.
- No duplicated logic for "which bars are lit" — the same function and the same data source must handle manual slider input, the post-scan lock-on, and the initial render.Step by step
How to Use
- 1Paste HTML, CSS, and JSFive bars render with a strength label beneath them.
- 2Drag the strength sliderBars light up to the level and the color shifts from red to green.
- 3Read the labelThe text updates from No signal through Excellent to match the bars.
- 4Press simulate scanningThe bars sweep blue while the meter hunts for a signal.
- 5Wait for lock-onAfter a beat it stops and snaps to a random realistic strength.
- 6Map real dataConvert RSSI or connection type to 0–5 and call paint().
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Each bar is a sibling span, and :nth-child selectors set their heights to 20%, 40%, 60%, 80%, and 100%. The container aligns them to the bottom with align-items:flex-end, so they form the familiar ascending signal shape using only five empty elements and CSS.
A LEVELS array maps each strength 0–5 to a name and a color. The paint() function reads one entry and applies it everywhere at once — lighting the bars, setting the --sig custom property, and writing the label text and aria-label — so the meter and the words always match.
It adds a .scanning class that runs a sigScan keyframe on each bar with an increasing animation-delay, producing a left-to-right blue sweep like a device searching. The slider is disabled during the scan, and a setTimeout ends it after about 2.2 seconds by snapping to a random realistic strength.
Yes. Map any metric — RSSI in dBm, the Network Information API effectiveType, or a latency bucket — onto a 0–5 level and call paint(). Since rendering is decoupled from the source, you can poll, listen to navigator.connection change events, or stream telemetry without changing the view.
Render the five bars from an array and add the lit class when the index is below the current level. Keep the level in state and derive the color and label from the LEVELS map. Put the scanning setTimeout in an effect and clear it on cleanup. In Tailwind, set bar colors via an inline style bound to the --sig value.