Dates & Times

Show dates, times, ranges, "2 hours ago" and lengths of time the same way everywhere in your app.

Invoice date:

Sent:

Stay: Oct 14 – 20, 2026

Call length:

Last seen:

import { DateRangeText } from "@hel/ui/date-range-text"
import { DateText } from "@hel/ui/date-text"
import { DurationText } from "@hel/ui/duration-text"

When to use it

Use these components wherever the app writes a date or a time: an invoice date, the time of a comment, a booking range, how long a call lasted. They all read one setting for the language, digits, calendar and time zone, so the dates in a table, a dialog and an email preview all look the same, and change together.

Do not build dates with toLocaleDateString in each place. The results drift apart, and a new language means finding every one of them. For picking a date, use the Date Picker.

Import

import { DateText } from "@hel/ui/date-text"
import { DateRangeText } from "@hel/ui/date-range-text"
import { RelativeTime } from "@hel/ui/relative-time"
import { DurationText } from "@hel/ui/duration-text"

Usage

Each one is a piece of Text, so it takes size, weight, tone and the rest, and it can sit inside a sentence.

<DateText value={invoice.postedAt} format="datetime" />
<DateRangeText from={booking.start} to={booking.end} />
<RelativeTime value={comment.createdAt} />
<DurationText value={call.length} />

DateText, DateRangeText and RelativeTime render a <time> element that carries the exact value, which search engines and screen readers can read. An empty or invalid value shows a dash, or the emptyText you give.

What counts as a date

A Date, an ISO string, or milliseconds since 1970. There is one case to know about. A string with only a day, such as "2026-10-14", is a calendar day, and it shows as the 14th in every time zone. A string with a time and a Z or an offset is a moment, and it shows in the time zone of the app. Send offsets from your API, because a time without one is read in the time zone of the browser.

Examples

Formats

format is the name of a format, or an Intl.DateTimeFormatOptions object. These are the names that come with the library, shown for the same moment. The iso ones always use Latin digits and the Gregorian calendar, so they suit exports and file names.

date

dateShort

dateLong

dateFull

dateNumeric

time

datetime

monthYear

dayMonth

weekday

iso

isoDateTime

import { DateText } from "@hel/ui/date-text"
import { Flex } from "@hel/ui/flex"
import { Stack } from "@hel/ui/stack"

You can add your own names in the config under dates.formats, such as invoiceDate, and use them anywhere. A day on its own, such as "2026-10-14", has no time, so a format that writes a time shows the empty text for it.

Relative times

RelativeTime writes "2 days ago" or "in 3 hours", and it keeps itself right: the text changes when it should, and all of them on a page share one timer. The exact date and time show on hover, and in the <time> element. tones colors the past, the future and the same moment, so an overdue date can be red. absoluteAfterDays switches to a date after some days, because "9 days from now" is harder to use than a date.

import * as React from "react"

import { Flex } from "@hel/ui/flex"

display is long, short or narrow. For tests and server rendering, pass now, so the text does not depend on the time of the machine. A value with now never updates itself.

Ranges and lengths of time

DateRangeText writes what the two ends share only once, and keeps two calendar days as days. DurationText takes milliseconds, an ISO 8601 string such as "PT2H30M", or parts such as { hours: 2, minutes: 30 }. display is long, short, narrow or digital. By default it shows the two largest units, which you can change with maxUnits.

long

short

narrow

digital

Same month

Oct 14 – 20, 2026

Across months

Oct 28 – Nov 3, 2026

Across years

Dec 28, 2026 – Jan 3, 2027
import { DateRangeText } from "@hel/ui/date-range-text"
import { DurationText } from "@hel/ui/duration-text"
import { Flex } from "@hel/ui/flex"

Language, digits and time zone

Everything above follows the config of the app. A single use can override it with locale, numberingSystem, calendar, timeZone and hourCycle. This is for a case like showing the time in the time zone of a venue, and not for the language of the app.

English

German

Arabic, Latin digits

Arabic, Arabic digits

import { DateText } from "@hel/ui/date-text"
import { Flex } from "@hel/ui/flex"
import { Stack } from "@hel/ui/stack"

Props

DateText

PropTypeDefault
valuea date, an ISO string or millisecondsrequired
formata format name or Intl.DateTimeFormatOptions"date"
renderParts(parts, result) => ReactNodenone
emptyTextReactNodea dash

renderParts lets you write the pieces yourself, such as a muted time next to a bold day.

DateRangeText

PropTypeDefault
from / toa date, an ISO string or millisecondsrequired
formata format name or Intl.DateTimeFormatOptions"date"

RelativeTime

PropTypeDefault
valuea date, an ISO string or millisecondsrequired
display"long" | "short" | "narrow"from the config
tones{ past?, future?, now? }, each a tonenone
absoluteAfterDaysnumber | falsefrom the config
absoluteFormata format name or optionsfrom the config
numeric"auto" | "always"from the config
showExactbooleantrue
updatebooleantrue
nowDate | numberthe current time

With numeric="auto" you get "yesterday" and "tomorrow", and with "always" you get "1 day ago". Each text also carries data-tense as past, future or now for your own styling.

DurationText

PropTypeDefault
valuemilliseconds, an ISO 8601 string or partsrequired
display"long" | "short" | "narrow" | "digital"from the config
maxUnitsnumber2
largestUnit / smallestUnita unitnone
unitthe unit of a plain numbermilliseconds

Shared by all four

locale, numberingSystem, calendar, timeZone, hourCycle and emptyText, plus every prop of Text.