RelativeTime
When something happened, in the one notation the whole suite agrees on.
Why it exists
Eight copies of this function existed across the suite — ago() in Echo, Ivy, Root, Sage and Bloom, relative() in Seed and Grove, one more inlined in Branch's issue table. They did not merely duplicate each other; they disagreed, and someone moving between two Trellis apps during an incident had to re-read the same fact in a different notation.
| The fact | Before | Now |
|---|---|---|
| 40 seconds ago | Echo 40s ago · Sage just now | |
| 90 seconds ago | Root 1m ago · Sage 2m ago | |
| 26 hours ago | Bloom 26h ago · Seed 1d ago | |
| 418 days ago | Root 418d ago · Seed 2025-07-17 | |
| no timestamp | Root never · Echo — | |
| four hours from now | all three: just now |
The scale
One unit, floored, and an absolute date past a fortnight. Hover any of these — the exact UTC instant is in the title, so a column stays scannable without the precision going missing.
Forward, for a deadline
future flips the scale round. Anything already due says so rather than counting down past zero — 'in 0m' reads as a countdown that has stalled.
The absolute form
For an incident timeline, an audit row, anywhere two timestamps are compared to each other. Monospace and tabular, because the digits have to line up or the comparison is eyeballing rather than reading.
In both themes
light
dark
Guidance
Use it when
- A column of records where the age is what you are scanning for.
- future for anything with a deadline — a rotation, an expiry, a scheduled send.
- Timestamp when two instants will be compared to each other.
Reach for something else when
- A duration. That is duration(ms), which shows two units because a span is the measurement rather than a label on one.
- Anywhere the exact instant is the point and hovering is not possible — print it.
- Live ticking. This renders once; a value that changes every second is a hydration mismatch waiting to happen.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | Date | string | number | null | — | The instant. A Date, an ISO string, or epoch milliseconds. |
| future | boolean | false | Count forward — “in 4h” — for a deadline rather than a record. |
| now | Date | number | — | Pins the clock. For tests, and for a page rendering many rows off one instant. |
| fallback | string | "never" | Shown when there is no timestamp. “—” when absence is not a fact. |
Accessibility, and hydration
It renders a real <time dateTime>, so the machine-readable instant travels with the human one — it survives being copied, scraped, or read by a screen reader that announces dates in the user's own format.
It also carries suppressHydrationWarning. This renders on the server and hydrates milliseconds later, and the two can land either side of a minute boundary. Both values are correct; only React minds. The suppression covers this element's text and nothing else — which is also why the scale stops at whole minutes rather than counting seconds.