Relative time
Timestamps like 5 minutes ago that stay true, in the reader's language, without a timer per second.
pnpm dlx shadcn-svelte@latest add https://coral.imlargo.dev/r/kit-relative-time.jsonnpx shadcn-svelte@latest add https://coral.imlargo.dev/r/kit-relative-time.jsonbunx --bun shadcn-svelte@latest add https://coral.imlargo.dev/r/kit-relative-time.jsonyarn dlx shadcn-svelte@latest add https://coral.imlargo.dev/r/kit-relative-time.jsonrelativeTime(date) helpers get written once per project, and they share the same limits: the text
is computed at render and goes stale while the page is open, "60 minutes ago" shows up where "1 hour
ago" belongs, and the fix (a setInterval per timestamp) wakes a two-hundred-row table every
second to change nothing.
- Build passed
- Deployed to production
- Issue closed
- API key rotated
- Maintenance window
<script lang="ts">
import RelativeTime from '#lib/components/coral/kit/relative-time/relative-time.svelte';
const minute = 60_000;
const now = Date.now();
const events = [
{ what: 'Build passed', at: now - 20_000 },
{ what: 'Deployed to production', at: now - 7 * minute },
{ what: 'Issue closed', at: now - 5 * 60 * minute },
{ what: 'API key rotated', at: now - 26 * 60 * minute },
{ what: 'Maintenance window', at: now + 3 * 24 * 60 * minute }
];
</script>
<ul class="flex w-80 flex-col gap-2 text-sm">
{#each events as event (event.what)}
<li class="flex justify-between gap-4">
<span>{event.what}</span>
<RelativeTime date={event.at} class="text-muted-foreground" />
</li>
{/each}
</ul>What Coral adds
- Stays current, cheaply. Each instance sleeps until the exact moment its own text changes: "3 days ago" wakes up in hours, not every second. A tab brought back to the foreground refreshes at once, since background timers are throttled.
- Right units: the unit is picked on the rounded value, so 59 minutes 40 seconds is "1 hour ago". Four weeks never sits next to one month.
- Worded by
Intl. "yesterday", "in 3 days", "hace 5 minutos", "5 分前": any locale, inlong,shortornarrow. - A real
<time>:datetimecarries the instant for machines andtitlethe full date for whoever hovers. - Symmetric. 2.5 minutes ago and in 2.5 minutes both round to 3.
Live
You opened this page .
Short form:
<script lang="ts">
import RelativeTime from '#lib/components/coral/kit/relative-time/relative-time.svelte';
const openedAt = new Date();
</script>
<div class="flex flex-col items-center gap-1 text-sm">
<p>You opened this page <RelativeTime date={openedAt} precision="second" />.</p>
<p class="text-muted-foreground">
Short form: <RelativeTime date={openedAt} format="short" precision="second" />
</p>
</div>precision="minute", the default, reads anything within half a minute as "now", since a label
ticking every second draws the eye for no reason. Use second where the seconds matter.
Past a cutoff
- 2 days ago
- 40 days ago
<script lang="ts">
import RelativeTime from '#lib/components/coral/kit/relative-time/relative-time.svelte';
const day = 24 * 60 * 60 * 1000;
const week = 7 * day;
</script>
<ul class="flex w-80 flex-col gap-2 text-sm">
<li class="flex justify-between">
<span>2 days ago</span>
<RelativeTime date={Date.now() - 2 * day} cutoff={week} />
</li>
<li class="flex justify-between">
<span>40 days ago</span>
<RelativeTime
date={Date.now() - 40 * day}
cutoff={week}
titleFormat={{ dateStyle: 'medium' }}
/>
</li>
</ul>"40 days ago" is rarely what anyone wants to read about a deployment. Past cutoff milliseconds
the date is shown in full instead, formatted with titleFormat, and nothing is scheduled for it.
Import
<script lang="ts">
import RelativeTime from '#lib/components/coral/kit/relative-time/relative-time.svelte';
</script>Props
Everything a <time> accepts stays available: class, aria-*, id. On top of that:
| Prop | Type | Default | Description |
|---|---|---|---|
date | Date | string | number | - | The moment to describe. Required. |
locale | string | en-US | Wording and absolute date. |
numeric | 'auto' | 'always' | auto | "yesterday" versus "1 day ago". |
format | 'long' | 'short' | 'narrow' | long | Length of the wording. |
precision | 'second' | 'minute' | minute | Smallest unit shown. |
live | boolean | true | Keeps the text current. |
cutoff | number | - | Milliseconds past which the full date shows. |
titleFormat | Intl.DateTimeFormatOptions | { dateStyle: 'long', timeStyle: 'short' } | The absolute date's format. |
now | Date | - | Pins the clock - tests, previews, snapshots. |
children | Snippet<[{ text, absolute, date }]> | - | Replaces the text inside <time>. |
relative.ts
describe and nextChange are exported for use outside a component, such as a label in an email or
a server-rendered feed:
import { describe } from '#lib/components/coral/kit/relative-time/relative.js';
const { value, unit } = describe(new Date(comment.createdAt), new Date());
// `undefined` is the reader's own locale; pass one to pin it.
new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' }).format(value, unit); // "5 minutes ago"Months and years are average lengths, so a relative label is approximate by nature. For "on 12 March", use the absolute date.