Source: https://monarchui.dev/components/relative-time/

Components · Data display

# RelativeTime

A localised "3 hours ago" in a <time> element, with the absolute time on hover and focus.

@rookcom/monarch-react

Component

## Relative and absolute

## Usage examples

### Short, narrow and live

### Server rendering

## Purpose

Shows when something happened (or will happen) relative to now, in the reader’s language, while keeping the exact moment one hover or Tab away and machine-readable in `datetime`.

### Use when

- Activity feeds, inbox rows, comments and “last synced” labels where recency matters more than the exact time.
- A status that should keep itself current (`live`), such as “Updated 2 minutes ago”.

### Avoid when

- The exact date is what the reader needs (invoices, contracts, due dates): format the date itself.
- A countdown that must be precise to the second: build it on a timer and an `aria-live` policy of your own.

## Design principles

- The exact time is never only in a `title`: the tooltip opens on hover and on keyboard focus and is linked with `aria-describedby`.
- Localised by `Intl.RelativeTimeFormat` and `Intl.DateTimeFormat`; no hand-written “ago” strings.
- Server rendering is deterministic when you pass `now`; without it, the client corrects the text after hydration instead of warning.
- Live updates are quiet: the text changes without a live region, so screen readers are not interrupted every minute.

## API reference

```tsx
import { RelativeTime, formatRelativeTime, relativeTimeParts } from '@rookcom/monarch-react';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `RelativeTime` | component | client | `.mn-relative-time` |
| `formatRelativeTime` | utility | client | – |
| `relativeTimeParts` | utility | client | – |

The ref and native attributes go to the `<time>` element; `className` goes to the `mn-relative-time` wrapper that also holds the tooltip.

`formatRelativeTime(date, options)` and `relativeTimeParts(date, now)` are exported for places that need the string, such as table exports or `aria-label`s.

Generated from the TypeScript declarations of `packages/react/src/components/data-display/relative-time`.

## Properties

`date` and `now` accept a Date, epoch milliseconds or an ISO 8601 string.

### `RelativeTime`

A `<time datetime>` with localised relative text ("3 hours ago") and the absolute time on hover and focus.

| Prop | Type | Description |
| --- | --- | --- |
| `date` required | `DateInput` | The moment to describe. A Date, epoch milliseconds or an ISO 8601 string. |
| `absoluteAfter` | `number` | Show the absolute date instead of relative text once the moment is more than this many milliseconds away, e.g. `7 * 86_400_000` for "a week". |
| `absoluteFormat` | `Intl.DateTimeFormatOptions` | `Intl.DateTimeFormat` options for the absolute time. Default `{ dateStyle: 'medium', timeStyle: 'short' }`. |
| `className` | `string` |  |
| `format` | `"long" \| "short" \| "narrow"` | Unit length: `long` "3 minutes ago", `short` "3 min. ago", `narrow` "3m ago". Default `long`. |
| `id` | `string` |  |
| `live` | `number \| boolean` | Re-render as time passes: `true` picks an interval from the age (5 s for the first minute, then 30 s, 5 min and 1 h); a number is a fixed interval in milliseconds. Default `false`. |
| `locale` | `string` | BCP 47 locale for both the relative and the absolute text. Default: the runtime locale. |
| `now` | `DateInput` | The reference time. Pass it from the server (and the same value on the client) for byte-identical server rendering; without it the text is computed from the clock and corrected right after hydration. |
| `numeric` | `"auto" \| "always"` | `auto` writes "yesterday" and "now"; `always` writes "1 day ago". Default `auto`. |
| `timeZone` | `string` | IANA time zone for the absolute text, e.g. `Europe/London`. Default: the viewer's zone. |
| `tooltip` | `boolean` | Show the absolute time in a tooltip on hover and keyboard focus. Makes the timestamp a tab stop. Default `true`; set `false` in dense lists where the absolute time is shown elsewhere. |

## Variants

| Prop | Values | Description |
| --- | --- | --- |
| `format` | `'long'``'short'``'narrow'` | “3 minutes ago”, “3 min. ago”, “3m ago” (exact text depends on the locale). |
| `numeric` | `'auto'``'always'` | `auto` writes “yesterday” and “now”; `always` writes “1 day ago”. |
| `tooltip` | `'true'``'false'` | Absolute time on hover and focus (a tab stop), or plain text. |

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Relative | – | Default: the largest unit that fits, after rounding. |
| Absolute | – | Past `absoluteAfter` milliseconds the absolute date is shown and the tooltip is dropped. |
| Tooltip dismissed | `data-dismissed` | Set by Escape while focused; cleared on blur or when the pointer leaves. |
| Invalid date | `data-invalid` | An unparseable `date` renders an em dash and no `datetime`. |

## Interaction behaviour

- Hovering the text or the tooltip keeps the tooltip open; the pointer can move onto it (WCAG 1.4.13).
- With `live`, the text re-renders every 5 s for the first minute, then every 30 s, 5 min and 1 h as it ages; a number sets a fixed interval.
- Passing `now` freezes the reference time, which turns `live` off.

## Keyboard support

| Keys | Action |
| --- | --- |
| Tab | Focuses the timestamp and shows the absolute time (when `tooltip`). |
| Escape | Hides the tooltip without moving focus. |

## Accessibility

Role

time (with a role="tooltip" description)

Accessible name

The relative text; the absolute time is its description through

aria-describedby

.

- `datetime` carries the ISO 8601 instant for machines and assistive technology.
- The tooltip shows on `:focus-visible`, not only on hover, and can be dismissed with Escape.
- Use `tooltip={false}` in dense lists to avoid a tab stop per row when the exact time is available elsewhere.

## Responsive behaviour

- The text does not wrap. The tooltip is at most 18rem wide and never wider than the viewport minus 2rem.
- The tooltip is positioned with CSS above the text; inside a container that clips overflow it can be cut off, so give such containers room or use `tooltip={false}`.

## Theme support

- The tooltip uses the inverse surface tokens, like Tooltip, so it reads in light, dark and more-contrast themes.
- The dotted underline uses the current text colour at reduced opacity.
- The tooltip fade is removed under reduced motion.

## Do and don't

### Do

- Pass the viewer’s `timeZone` when you know it, so the tooltip shows their local time.
- Use `absoluteAfter` for old items, where “5 months ago” is less useful than the date.
- Pass `now` when rendering on the server if the HTML must be identical to the hydrated result.

### Don't

- Do not put essential information only in the tooltip.
- Do not wrap a live RelativeTime in an `aria-live` region; it would announce every tick.
- Do not build “x ago” strings by hand next to it; use `formatRelativeTime`.

## Related components

- [Tooltip](https://monarchui.dev/components/tooltip/): Rich supplementary content on hover and focus.
- [ActivityTimeline](https://monarchui.dev/components/activity-timeline/): Planned feed that shows a RelativeTime per event.
- [DescriptionList](https://monarchui.dev/components/description-list/): Label and value pairs such as “Last updated”.
- [DatePicker](https://monarchui.dev/components/date-picker/): Planned input for choosing a date rather than showing one.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `react` |
| Monarch components | None |

## Testing coverage

- Unit selection rounds before rolling over (59.6 min reads “1 hour ago”).
- Formats with `Intl.RelativeTimeFormat`, including `numeric`, `format` and other locales.
- Renders `<time datetime>` and an absolute tooltip linked by `aria-describedby`.
- Accepts ISO strings, epoch numbers and Date objects.
- Is a tab stop; Escape dismisses the tooltip and blur resets it.
- `tooltip={false}`, `absoluteAfter`, `timeZone` and invalid dates.
- Live updates tick with fake timers; no ticking without `live`.
- Forwards the ref and calls consumer handlers.
- Server rendering with `now`, and hydration with a different client clock without errors.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery; Cross-browser axe: /components/relative-time |

Tests: `packages/react/src/components/data-display/relative-time`

## All example code

### Relative and absolute

```tsx
import { RelativeTime } from '@rookcom/monarch-react';

const NOW = Date.UTC(2026, 10, 3, 12, 0, 0);

const MIN = 60_000;

const HOUR = 60 * MIN;

const DAY = 24 * HOUR;

<div>
  Edited <RelativeTime date={NOW - 3 * MIN} now={NOW} locale="en-GB" timeZone="UTC" />
</div>
<div>
  Synced{' '}
  <RelativeTime date={NOW - 5 * HOUR} now={NOW} locale="en-GB" timeZone="UTC" />
</div>
<div>
  Renews <RelativeTime date={NOW + 2 * DAY} now={NOW} locale="en-GB" timeZone="UTC" />
</div>
<div>
  Created{' '}
  <RelativeTime
    date={NOW - 40 * DAY}
    now={NOW}
    locale="en-GB"
    timeZone="UTC"
    absoluteAfter={7 * DAY}
    absoluteFormat={{ dateStyle: 'medium' }}
  />
</div>
```

### Short, narrow and live

```tsx
import { RelativeTime } from '@rookcom/monarch-react';

const NOW = Date.UTC(2026, 10, 3, 12, 0, 0);

const MIN = 60_000;

const HOUR = 60 * MIN;

const DAY = 24 * HOUR;

<div>
  <RelativeTime
    date={NOW - 3 * MIN}
    now={NOW}
    locale="en"
    format="short"
    tooltip={false}
  />
</div>
<div>
  <RelativeTime
    date={NOW - 3 * MIN}
    now={NOW}
    locale="en"
    format="narrow"
    tooltip={false}
  />
</div>
<div>
  <RelativeTime
    date={NOW - DAY}
    now={NOW}
    locale="en"
    numeric="always"
    tooltip={false}
  />
</div>
```

### Server rendering

```tsx
import { RelativeTime } from '@rookcom/monarch-react';

const NOW = Date.UTC(2026, 10, 3, 12, 0, 0);

const MIN = 60_000;

const HOUR = 60 * MIN;

<div>
  Localised: <RelativeTime date={NOW - 3 * HOUR} now={NOW} locale="de" timeZone="UTC" />
</div>
```
