webdevpuneet.com

Day.js Relative Time Feed — Free HTML CSS JS Snippet

Day.js Relative Time Feed · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

Copy & paste

Source Code

<div class="rf-wrap">
  <div class="rf-card">
    <div class="rf-head">
      <div class="rf-title">Activity Feed</div>
      <span class="rf-live"><span class="rf-dot"></span>Live</span>
    </div>
    <ul class="rf-list" id="rfList"></ul>
  </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

Genuinely live relative labels
fromNow() recomputes fresh, does not cache a frozen string.
Periodic re-render loop
A 30-second interval keeps every timestamp accurate.
Simulated live insertions
New items arrive the same way a WebSocket push would.
Always-correct chronological order
Re-sorted by real time on every render, not just on insert.
Consistent "fresh" detection
Uses the same now-vs-then comparison as the label itself.
Deterministic seed data
Backdated sample items spread realistically, not all "just now".

About this UI Snippet

Day.js Relative Time Feed — Why "2 Minutes Ago" Needs to Keep Recomputing

Screenshot of the Day.js Relative Time Feed snippet rendered live

A relative timestamp like "2 minutes ago" is only accurate at the instant it's computed — leave it on screen without ever recalculating, and ten minutes later it's still lying. This snippet's core discipline is treating fromNow() as something to call again periodically, not a string to compute once and forget.

relativeTime is a plugin, not core Day.js

Day.js ships a deliberately minimal core and adds capabilities like fromNow() through plugins — dayjs.extend(dayjs_plugin_relativeTime) is what registers it, and skipping that line means .fromNow() simply doesn't exist on a dayjs object, a common first-time gotcha with the library.

fromNow() always compares against the real current moment

Calling item.time.fromNow() computes the difference between that stored timestamp and dayjs()'s value *at the moment fromNow() is called* — it does not cache or memoize a string from whenever the item was first created. That's what makes the periodic setInterval(render, 30000) meaningful: re-rendering doesn't just redraw the same labels, it recomputes each one fresh, so "2 minutes ago" genuinely becomes "3 minutes ago" without a page reload.

The feed re-sorts by time on every render, not just on insert

Rather than assuming new items always belong at the top (true for genuinely live pushes, but this snippet also seeds several backdated items on load), render() sorts a copy of the items array by timestamp descending every time it runs — which is what guarantees the visual order always matches actual chronological order regardless of the mix of seeded and live-pushed items.

"Fresh" gets its own visual treatment, computed the same way as the label

An item's green "fresh" styling is decided by dayjs().diff(item.time, 'minute') < 1 — the exact same kind of now-versus-then comparison fromNow() does internally, just checked directly for a boolean instead of formatted as a string. Both pieces of information are always in agreement because they're derived from the same live comparison at render time, not two different clocks.

Reusing it

Point the "new event arriving" setInterval at a real WebSocket message handler or Server-Sent Events listener instead of Math.random(), keep the same dayjs()-timestamped insert-and-render pattern, and this becomes a genuine live activity feed.

Step by step

How to Use

  1. 1
    Add the Day.js CDNLoad dayjs.min.js and the relativeTime plugin before the snippet's JS runs.
  2. 2
    Paste HTML, CSS, and JSA feed renders with six backdated items, newest first.
  3. 3
    Watch a fresh itemA newly-added item shows a green "just now" style label.
  4. 4
    Wait about 30 secondsRelative labels like "2 minutes ago" tick forward on their own.
  5. 5
    Wait about 12 secondsA new simulated event arrives at the top of the feed.
  6. 6
    Watch the feed reorderItems always stay sorted by real chronological time.

Real-world uses

Common Use Cases

Social and notification feeds
Show the familiar X commented 3 minutes ago style labels, recomputed with fromNow() every 30 seconds so they never freeze at the moment of creation.
Admin audit and activity logs
Keep recent-events views accurate for hours, with items re-sorted by real time on every render, not merely appended in arrival order.
Live chat message timestamps
Pair with the live realtime chart on an operations screen, so both numbers and activity labels stay fresh without a page reload.
Support ticket threads
Display accurate posted-time labels on comments and replies, with new items inserted the same way a WebSocket push would deliver them.
Day.js plugin reference
Use it as a clear example of the relativeTime plugin, including how to extend Day.js once and then call fromNow() anywhere.

Got questions?

Frequently Asked Questions

Day.js deliberately ships a minimal core library and moves less universally-needed features like relative time formatting into optional plugins, which keeps the base library small. Calling dayjs.extend(dayjs_plugin_relativeTime) registers that plugin's functionality (including the fromNow() method) onto every dayjs object — without that line, .fromNow() is simply not a method that exists yet.

fromNow() calculates its output by comparing a stored timestamp against dayjs()'s value at the exact moment fromNow() is called — it does not store or cache a fixed string when an item is first created. A label rendered once and left alone would become inaccurate as real time passes, so this snippet re-runs render() (which recomputes every fromNow() call) every 30 seconds to keep every displayed label truthful.

While a genuinely live push would always be newer than everything already in the feed, this snippet also seeds several backdated sample items when it first loads, mixed at various times in the past. Sorting a copy of the full array by timestamp on every render guarantees correct chronological order regardless of that mix, rather than relying on an assumption (new items are always newest) that happens to be true only for the live-push case.

Both the fresh check and the fromNow() label are computed as a live comparison against dayjs() (the current moment) at render time — the fresh check specifically computes dayjs().diff(item.time, "minute") and checks if it's under 1. Because both values derive from the same now-versus-then comparison at the same render pass, the green "fresh" indicator and the text label can never disagree about how recent an item actually is.

Replace the setInterval that generates random events with a handler for your real data source — a WebSocket onmessage callback or an EventSource listener — that calls the same items.unshift({ ..., time: dayjs() }) and render() pattern whenever a real event arrives. The periodic label-refresh interval and the sorting logic require no changes regardless of where new items come from.