GrRelativeTime

  • chrono
  • chrono
  • API · 9 props
import { GrRelativeTime } from '@feugene/granularity-chrono/components/GrRelativeTime'

Overview

When to take it

  • freshness matters more than precision — a feed, comments, notifications, an audit log: “5 minutes ago” reads faster than “12.08.2026, 14:32”;
  • the label has to stay correct — live recomputes the text, and “just now” does not hang there an hour later;
  • there are many labels on the page — a hundred labels cost one setInterval: the tick is shared and is removed on a hidden tab;
  • both forms are needed — the exact date travels into title and into the slot, so hovering and a screen reader give the absolute moment.

When to take something else

NeedTake
Choose a date rather than show oneGrDatePicker
Choose a moment with a timeGrDateTimePicker
Show a sequence of eventsGrTimeline
Show a countdown to a deadlinea wrapper of your own over useChronoNow

Exact values are not shown this way. A contract, a payment, a flight schedule require a date: “in 2 days” does not answer the question “on what date”. cutoff sets the threshold past which the component moves to the absolute form by itself — and for such places it is set rather than the live recomputation being switched off.

Examples 3 each with a live demo and its code

  • The activity of a workspace

    Freshness matters more than precision: «2 minutes ago» reads faster than a timestamp, and the exact moment stays in the title and the <time datetime> for hover and screen readers. value-adapter="isoDateTime" takes the API strings as they are, numeric="auto" says «yesterday», and base pins «now» for this frozen page, so the server and the browser agree.

    Workspace activity
    • MOMina Ortiz approved invoice INV-0418
    • DKDaniel Kim added 3 seats to the Team plan
    • BABilling API retried the card of Contoso Ltd
    • ATAiko Tanaka changed the dunning schedule
    • MOMina Ortiz invited Ben Hartley as an accountant
  • When invoices are due

    A due date is close to a contract: «in 5 days» works, «3 weeks ago» does not answer «which day». cutoff switches to the absolute date from 14 days on, formatted by format options rather than a pattern string. value-adapter="isoDate" marks the value as a day, so it is counted in calendar days — at 9 a.m. tomorrow’s date reads «tomorrow», not «in 15 hours». The default slot gets the text, so overdue rows say so in red.

    Contoso Ltd · open invoicesNet 14 · reminders go out on the due date
    • INV-0431€1,284.50
    • INV-0427€432.00
    • INV-0418€3,210.00
    • INV-0402€96.00
  • A label that stays true

    The last import is a real moment of this session, so live keeps the label true without anyone touching it: «now» becomes «1 minute ago» on a shared timer that ticks every 5 seconds while seconds matter, once an hour for months, and stops on a hidden tab. width="short" fits a status line.

    Deutsche Bank · business accountTransactions are matched to invoices on import
    Last import

API generated from the package · cannot drift

Props

PropTypedefaultDescription
valueTValue | null | undefinedundefinedThe moment in question. An empty value renders an empty label.
valueAdapter
"date" | "isoDate" | "isoDateTime" | "timestamp" | GrChronoA"date" | "isoDate" | "isoDateTime" | "timestamp" | GrChronoAdapter<TValue> | undefined
undefinedHow the value arrives from the consumer. The same contract as in the pickers.
baseDate | undefinedundefinedWhat to compare against instead of "now". Set — the clock is not read at all: no timer, no divergence between server and client rendering. That is also what makes tests and snapshots reproducible.
liveboolean | undefinedundefinedUpdate live. The component picks the tick itself from the current unit: "3 months ago" need not be recomputed every second.
cutoffnumber | undefinedundefinedFrom how many days on to show a plain date instead of a relative one. `0` — never: "347 days ago" stays as it is.
formatIntl.DateTimeFormatOptions | undefinedundefinedThe look of the absolute date — through `Intl` options, not a pattern string.
widthNameWidth | undefinedundefinedLength of the relative string: "3 months ago" versus "3 mo. ago".
numericIntl.RelativeTimeFormatNumeric | undefinedundefined`'auto'` gives "yesterday", `'always'` — "1 day ago".
localestring | undefinedundefinedDisplay locale. Unset — from the application's i18n adapter.

Slots

SlotTypeDescription
default{ text: string; absolute: string; datetime: string; }Custom markup instead of the text: the same values the component renders itself.

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrRelativeTimeConfigurableProps, GrRelativeTimeProps } from '@feugene/granularity-chrono/components/GrRelativeTime'

Filedist/types/components/GrRelativeTime/config.d.ts

grRelativeTimeConfigconst
const grRelativeTimeConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrRelativeTime">;

Filedist/types/components/GrRelativeTime/defaults.d.ts

GrRelativeTimeConfigurablePropsinterface
interface GrRelativeTimeConfigurableProps {
    width: NameWidth;
    numeric: Intl.RelativeTimeFormatNumeric;
    cutoff: number;
    live: boolean;
}

Filedist/types/components/GrRelativeTime/safelist.d.ts

grRelativeTimeSafelistconst
const grRelativeTimeSafelist: string[];

Filedist/types/components/GrRelativeTime/GrRelativeTime.vue.d.ts

GrRelativeTimePropsinterface
interface GrRelativeTimeProps<T = Date> {
    value?: T | null;
    valueAdapter?: GrChronoAdapterName | GrChronoAdapter<T>;
    base?: Date;
    live?: boolean;
    cutoff?: number;
    format?: Intl.DateTimeFormatOptions;
    width?: NameWidth;
    numeric?: Intl.RelativeTimeFormatNumeric;
    locale?: string;
}

Usage from the library prose

The unit is chosen in two ways, and that is visible at the boundaries

Up to a day the gap is measured by elapsed time — a second is a second, and the calendar has nothing to do with it. From a day onwards it is measured with calendar tuples: days, weeks, full months, years.

Two consequences follow. A month stays a month both in February and in July, because it is counted by the calendar. And a day that lasted 23 hours because of a clock change is called hours — because that much time really did pass; otherwise a two-hour gap across midnight would be called yesterday’s every night.

A date without a time is a day rather than midnight

A value without a time (valueAdapter="isoDate" or an adapter of your own with dateOnly: true) describes a day in the calendar, and its midnight is a convention of the parsing. Measured by elapsed time, tomorrow’s date would come out in the morning as “in 15 hours”, and yesterday’s as “9 hours ago”. Such a value is measured by the calendar alone, from today: “tomorrow”, “today”, “yesterday”, beyond that weeks and months. The datetime of the <time> receives the date itself (2026-10-07) rather than a moment in UTC: a local midnight east of Greenwich is the evening of the previous day in UTC.

"Now" is never in the future

The shared clock is updated by a tick, and between ticks its “now” lags behind the present. That is why the component rereads the clock when the value changes and when it moves to a ticker with another tick, rather than comparing with the last tick: otherwise something that has just happened would read as “in 4 seconds”, and a label that has grown into minutes would see seconds again on the lagging minute ticker and swing between the tickers. A future closer than the seconds tick (five seconds) is “now” as well: the source of the value is often a couple of seconds fast. A zero gap is printed as “now”, and with numeric="always" as “0 sec. ago”, but not as “in 0 sec.”. With base the comparison is with the given moment, and there is no tolerance: two seconds ahead is the future.

The tick depends on the unit

Seconds are recomputed once every five seconds, months once an hour. One tick for all of them would mean either a second jerking a hundred labels for nothing, or a minute because of which “just now” holds longer than it is true.