A timestamp that reads as “3 hours ago” and keeps itself current. The rendering is
@github/relative-time-element, which
already owns Intl.RelativeTimeFormat per locale and a re-render schedule that slows down
as the date ages - enigma add relative-time installs it for you.
What the package adds is the part every hand-rolled wrapper gets wrong.
Customize
The first control moves the date rather than a prop, because how old it is decides whether it reads as a phrase or a date. Set a locale and it speaks that language.
Usage
Installation
Adds the package and prints the import to use. Append --copy to write the source into your project instead.
enigma add relative-timeBrings @github/relative-time-element with it. --no-deps leaves them out.
Installs with your project's own package manager - npm, pnpm, yarn or bun - read from its packageManager field or its lockfile.
1 Install the dependencies
2 Use it
Import from @enigmax/primitives/react.
The stylesheet is only needed for capitalize and capitalizeFirst: the element writes its
own text content, so nothing JavaScript-side can transform it.
@import "@enigmax/primitives/relative-time.css";A timestamp with no zone is UTC
The single most common defect in a date column. 2026-08-13 22:41:00 comes back with no
offset, new Date() reads it as local time, and every reader east or west of the server
sees a time that is hours out - silently, because a wrong time is still a valid one.
<RelativeTime date="2026-08-13 22:41:00" /> // read as UTC, as the database meant it
<RelativeTime date="2026-08-13" /> // left alone: a date with no clock is already UTC
A date with no clock is deliberately untouched. Appending a zone to it produces
2026-08-13Z, which is outside the spec’s grammar and falls to each engine’s own legacy
parser - a portability coin flip on a value that was already correct.
It works before JavaScript does
The absolute date is rendered as the element’s child, so a server render, a page whose JavaScript has not arrived, and a reader with scripting off all see a real date. Once the element upgrades it renders the relative phrase into a shadow root and the child stops being shown - so the fallback costs nothing and is never seen twice.
<!-- what the server sends -->
<relative-time datetime="2026-08-13T22:41:00.000Z">on Aug 13, 2026</relative-time>
Pass now to pin the instant the age is measured from, and a server render and its
hydration agree exactly. Where you cannot, the component sets suppressHydrationWarning -
a timestamp is the one hydration difference React has an escape hatch for rather than a bug.
Props
| Default | ||
|---|---|---|
date |
ISO string, epoch ms, or a Date | |
format |
"auto" |
Relative until the threshold, then a date |
threshold |
"P30D" |
ISO 8601 duration. Past this, auto stops counting |
prefix |
"on" |
The word before an absolute date. "" removes it |
locale |
Undefined lets the element read the page’s own lang |
|
numericBeyondThreshold |
false |
Past the threshold, render digits instead of a month name |
capitalizeFirst |
true |
So a standalone “yesterday” starts a line properly |
fallback |
null |
Rendered when the date cannot be parsed |
numericBeyondThreshold reads the threshold rather than approximating it. The version this
came from used a fixed three-to-ninety-day window, which disagrees with threshold the
moment anyone sets one.