webdevpuneet.com

Live Visitor Counter — Real-Time Viewer Count UI

Live Visitor Counter · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

Copy & paste

Source Code

<div class="lvc-card">
  <span class="lvc-pulse-wrap">
    <span class="lvc-pulse-dot"></span>
  </span>
  <div class="lvc-text">
    <strong id="lvcCount">0</strong>
    <span id="lvcLabel">people viewing this page right now</span>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Random-walk count, not independent noise
Each update nudges the existing value rather than picking a fresh random number, producing a continuous, believable drift.
Irregular update timing
A randomized 2.2–4.8 second gap between updates avoids the unmistakably mechanical feel of a fixed-interval tick.
Occasional bigger jumps
A 15% chance of a ±2 change instead of ±1 adds natural-feeling variance to the drift pattern.
Enforced minimum count
The number never drops below 4, preventing an unbelievable near-zero "viewing right now" claim.
Zero-JS pulsing presence dot
A pure CSS ping animation built from one pseudo-element, requiring no extra DOM nodes or script.
Per-update bump animation
A forced-reflow class retrigger ensures the bump animation replays on every single change, even back-to-back ones.
Automatic singular/plural label
The label text itself switches between "person" and "people" based on the current count.
Compact, drop-anywhere pill
A self-contained inline pill component that fits naturally near a product price, CTA, or page header.

About this UI Snippet

Live Visitor Counter — Random-Walk Count, Pulsing Dot & Per-Update Bump

Screenshot of the Live Visitor Counter snippet rendered live

"23 people are viewing this right now" is a small but effective urgency cue used across e-commerce and ticketing sites — it signals active demand without the harder sell of a discount or a deadline. This snippet builds a believable live-counter widget: a pulsing presence dot, a count that drifts realistically rather than mechanically, and a tiny bump animation marking every change.

A random walk, not random noise

Rather than picking a fresh random number on every tick (which would jump around with no continuity and immediately look fake), step() nudges the *existing* count by ±1, occasionally ±2 on a 15% chance for a slightly bigger jump, clamped with Math.max(4, …) so it never drops to an unbelievable near-zero. This random-walk approach — each new value derived from the last, not generated independently — is what makes the number feel like it's tracking something real instead of visibly randomizing.

Irregular timing reads as more real than a fixed interval

Each update schedules the *next* one with setTimeout(step, 2200 + Math.random() * 2600) — a randomized 2.2–4.8 second gap — rather than a fixed setInterval. A perfectly regular tick is one of the easiest "this is fake" tells for any live-data widget; irregular timing, even though it's still simulated, matches how real, independent visitor arrivals and departures would actually look.

A pulsing dot built from one extra layer

The presence indicator is two stacked elements at the same position: a solid dot and a ::before pseudo-element of the same color that scales up to 2.6× while fading out on a 1.8-second loop — the classic CSS "ping" effect, requiring no JavaScript and no extra real DOM node beyond the one dot <span>.

A bump animation that announces the change without text noise

Every time the count updates, its number element gets a .bump class removed and immediately re-added (with a forced reflow via void countEl.offsetWidth so the animation retriggers even on consecutive updates) — a quick upward nudge-and-settle that draws the eye to the new value for an instant without a distracting color flash or a layout-shifting size change.

Use real data once you have it

This widget is built to demonstrate the interaction, not to manufacture demand — once connected to genuine concurrent-viewer data from real analytics, the exact same rendering and animation logic applies unchanged; only the source of the number differs. Treat the random-walk version as a placeholder for development and design review, not as something to ship in front of real visitors.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA pill-shaped counter renders with a pulsing green dot and a starting count between 23 and 34.
  2. 2
    Watch it driftEvery few seconds (an irregular 2.2–4.8s gap), the count nudges up or down by 1 or 2, with a small bump animation marking each change.
  3. 3
    Notice the floorThe count never drops below 4, however many consecutive decreases occur in a row.
  4. 4
    Check the singular/plural labelIf the count ever reaches exactly 1, the label automatically switches from "people" to "person."
  5. 5
    Adjust the starting range or drift sizeChange the initial count formula or the ±1/±2 delta logic in step() to tune how active or volatile the counter feels.
  6. 6
    Connect a real visitor countReplace the random-walk step() logic with a value from your real-time analytics (a WebSocket or short-poll endpoint), keeping the same bump-animation update call.

Real-world uses

Common Use Cases

Product page urgency cues
Show a pulsing X people are viewing this right now line on a product page, signalling active demand without a discount or deadline.
Ticket and event sales
Display live interest in a popular show alongside a stock urgency bar, with a believable random walk instead of independent noise.
Property and rental listings
Signal demand for a popular listing, where a 15% chance of a plus or minus two change adds natural variation between mostly small steps.
Course and webinar sign-ups
Pair with a social proof popup on a registration page, with updates arriving at irregular gaps between 2.2 and 4.8 seconds.
Flash sale countdown pages
Reinforce urgency next to a countdown timer, with a minimum of four visitors so the number never looks implausibly low.
Related: Podcast Episode Chapters
See the Podcast Episode Chapters for a related dashboards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Replace the random-walk logic in step() with a value from your analytics provider — either a WebSocket pushing live concurrent-viewer counts, or a short-poll fetch every few seconds — and call the same textContent/bump-class update with the real number instead of the simulated delta.

Many regions and platforms (and several countries' advertising/consumer-protection rules) treat fabricated scarcity or demand signals as a deceptive practice if presented as real data — use simulated numbers only for demos, mockups, or prototypes, and connect this to genuine analytics before shipping it to real users.

Check the current time (or fetch a server-provided value) before starting the step() loop, and conditionally hide the whole .lvc-card element (or skip rendering it) outside your desired active window.

Key your real visitor-count data by product id and pass the relevant count into this component's update logic per product card, rendering one counter instance per product rather than one global page-level counter.

In React, keep count in useState and run the randomized setTimeout loop inside useEffect with cleanup on unmount; in Vue, use ref()/onUnmounted for the same timer cleanup; in Angular, use a component field with ngOnDestroy. The random-walk and bump-animation-retrigger logic is plain JavaScript and ports directly.