relative-time-element

Localized, auto-updating relative timestamps as a drop-in <time> element replacement, no framework required.

Library
npm
v5.3.1
4,031stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
87/100Excellent
Development Activity88
Maintenance80
Community80
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
81/100Excellent
Architecture85
Code Quality88
Innovation72
Learning Curve80

relative-time-element is a small, framework-agnostic web component that extends the standard HTML <time> element to display timestamps as localized, human-readable relative text (“20 minutes ago”, “in 3 days”) that keeps itself updated in the browser without any polling code from the host page. Because the server only ever needs to emit a plain ISO 8601 datetime attribute and a static fallback text, HTML fragments stay cacheable while every visitor still sees a correctly localized, timezone-aware time once JavaScript takes over.

Under the hood it wraps the standard Intl.DateTimeFormat and Intl.RelativeTimeFormat APIs (with a small ponyfill for the newer Intl.DurationFormat proposal) and schedules re-renders through a single shared timer that throttles its own cadence as elapsed time grows, so a page with hundreds of <relative-time> elements does not spin up hundreds of independent intervals. Locale, time zone, and hour-cycle can all be inherited from ancestor elements or the document, letting one page mix per-user or per-widget formatting rules.

What You Get

  • A <relative-time> custom element you drop straight into server-rendered HTML, with the cached fallback text shown until JavaScript upgrades it
  • Three output formats — relative (“3 hours ago”), datetime (a localized absolute date), and duration (a countdown/count-up breakdown) — switchable via a single format attribute
  • Automatic, self-throttling re-render scheduling shared across every instance on the page via one internal timer, instead of a per-element interval
  • Locale, time-zone, and hour-cycle resolution that falls back from the element’s own attributes to the nearest ancestor to the document, so one page can mix formatting rules
  • A relative-time-updated event fired on every re-render so host code can react to text/title changes without polling

Common Use Cases

  • Rendering “posted 2 hours ago” / “updated just now” timestamps on cached server-rendered pages (comments, activity feeds, commit lists) without invalidating the HTML cache per viewer
  • Showing countdowns or elapsed durations for deadlines, expirations, or running processes via format="duration"
  • Displaying fully localized absolute dates (format="datetime") that still respect the visitor’s browser locale and timezone preferences
  • Adding accessible, auto-updating timestamps to any static site or non-React/Vue stack, since it’s a plain custom element with no framework dependency

Under The Hood

Architecture The library is a single custom element class (RelativeTimeElement in src/relative-time-element.ts) that extends HTMLElement and drives its behavior entirely through observedAttributes/attributeChangedCallback lifecycle hooks rather than a virtual-DOM diffing layer. Rendering, duration math, and locale-aware formatting are deliberately split into separate modules: duration.ts implements a Temporal-proposal-style Duration value type (parsing/comparing/applying ISO 8601 durations), intl-cache.ts owns a bounded LRU cache of Intl.DateTimeFormat/Intl.RelativeTimeFormat/Intl.NumberFormat/Intl.Locale instances keyed by locale+options, and relative-time-element-define.ts handles idempotent customElements.define registration. Re-render scheduling is centralized in a single module-level dateObserver object that every live element registers with; it computes the soonest next-update time across all observed elements and drives them from one shared setTimeout, so per-instance elapsed-time granularity determines cadence (seconds while recent, hours once stale) without one timer per element. If the shared Duration/intl-cache modules changed shape, every consumer of relative/duration/datetime formatting would break simultaneously, since all three formats route through them.

Tech Stack Written in TypeScript (strict tsconfig.json) targeting ES modules, compiled with tsc and bundled to a single ESM file with esbuild --bundle --keep-names. It has zero runtime dependencies — it only leans on browser-native Intl.DateTimeFormat/Intl.RelativeTimeFormat, with a hand-written ponyfill (duration-format-ponyfill.ts) standing in for the not-yet-universal Intl.DurationFormat. Custom-elements documentation is auto-generated via @custom-elements-manifest/analyzer into custom-elements.json, and linting runs through GitHub’s own shared ESLint configs (eslint-plugin-github, eslint-plugin-custom-elements) plus tsc --noEmit.

Code Quality Test coverage is unusually deep for the library’s size: the test/ directory (~4,000 lines) is roughly 3x the size of src/ (~1,300 lines), covering the element’s attribute/format matrix, the Duration parser/comparator, the Intl.DurationFormat ponyfill, and the LRU intl-cache eviction behavior, run via @web/test-runner against real Playwright-driven Chromium rather than a DOM shim. Errors are handled defensively at formatting boundaries (e.g. isBrowser12hCycle and locale resolution both wrap Intl calls in try/catch and fall back to safe defaults rather than throwing into the DOM lifecycle). Naming is consistent and privacy is enforced with real private class fields (#renderRoot, #updating), and CI runs the full test suite plus lint/typecheck on every push and PR via GitHub Actions.

API Design The element’s public surface is entirely HTML attributes and DOM properties mirroring native <time>-like conventions (datetime, plus opt-in format, tense, precision, threshold, and formatting attributes that map 1:1 onto Intl.DateTimeFormat option names), so anyone who knows Intl.DateTimeFormat already knows most of this API. Getting started requires zero JavaScript — one <script type="module"> import and a <relative-time datetime="..."> tag — with framework-specific behavior (locale/timezone inheritance from ancestors, a custom relative-time-updated event) layered on as progressive enhancement rather than required ceremony.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search