Principles
- Medium is the default; reserve short numerical for accounting and reporting.“5 May 2026” never gets misread as month-day or day-month; “05/05/2026” can. Reach for
d MMM yyyyin every product surface — table cells, sidebars, list rows, body copy, hero metrics. Short numerical is reserved for accounting + reporting contexts where line length is genuinely scarce or the output is machine-read: receipts, printed reports, XLS exports. Logs and audit trails use ISO 8601, never short numerical. - Never break dates or periods across lines. “27 Jan” is one token; “25 – 28 Jan” is one token. Wrapping the day onto one line and the month onto the next reads as two separate dates and forces a re-parse. The Table component defaults
noWrap: trueprecisely because of this rule; in body copy use a non-breaking space inside short formats. Truncate with an ellipsis before you wrap. - 24-hour clock by default.Across all our European markets and inside the product UI everywhere — including the US locale once it ships. The 12-hour clock with am/pm is only used in US marketing copy and system surfaces that mirror the user's OS preference.
- Relative dates close in, absolute dates further out. “2 minutes ago” is friendlier than 14:32; “5 May 2025” is friendlier than “372 days ago”. The crossover is 7 days.
- Always disambiguate time zoneswhen a number could mean two different moments — meeting times, deadlines, scheduled jobs. Append a short zone tag (CET, BST, UTC) or convert to the user's zone with a tooltip showing the original.
Default formats
These are the formats source copy is written in (en-GB) before localisation. Renderers swap them per locale at runtime.
Dates
Long
5 May 2026
Default for body copy, headings, and any place a date is read as a sentence. No commas, no leading zero on the day.
d MMMM yyyy
Medium · default
5 May 2026
The default everywhere. Three-letter month locks the day-or-month order at a glance, so it reads correctly in every locale. Reach for it in table cells, sidebars, list rows, body copy, and hero metrics — anywhere a date is meant to be read by a person.
d MMM yyyy
Short numerical
05/05/2026
Accounting and reporting only — receipts, printed reports, XLS / CSV exports. Never in product UI: a customer reading 05/05/2026 has to guess the month-day order, and that's a receipt-only tax we don't pay on screen. Always day-first; locale-flips to MM/DD/YYYY in the US.
dd/MM/yyyy
ISO 8601
2026-05-05
Technical surfaces only — logs, audit trails, API responses, anything sortable. Never customer-facing.
yyyy-MM-dd
Day of week
Tuesday, 5 May
Calendar surfaces and meeting confirmations. Drops the year when it's the current year.
EEEE, d MMMM
Times
Time
14:30
Default everywhere. Two digits with a colon. No leading 'at'.
HH:mm
Time with seconds
14:30:42
Logs, transaction timestamps, anywhere precision matters.
HH:mm:ss
Date and time
5 May 2026, 14:30
Default joined format. Comma between, never the word 'at'.
d MMM yyyy, HH:mm
Time with zone
14:30 CET
When the audience spans time zones. Three-letter zone abbreviation in caps.
HH:mm zzz
12-hour (US only)
2:30 pm
US-localised surfaces only. Lowercase 'am' / 'pm' with a non-breaking space, no periods.
h:mm a
Relative dates
A short scale, ordered from now to far past. The product reaches for the relative form when it's shorter than the absolute one and the reader doesn't need precision.
< 1 minute
Just now
Just now
1 – 59 minutes
N minute(s) ago
14 minutes ago
1 – 23 hours
N hour(s) ago
6 hours ago
Yesterday
Yesterday at HH:mm
Yesterday at 18:42
2 – 6 days
EEEE at HH:mm
Friday at 09:15
7+ days, this year
d MMM
3 May
Older
d MMM yyyy
3 May 2024
Hover for the absolute, always
Wrap relative dates in a <time> element with the ISO datetime in the dateTime attribute, and surface the absolute on hover. Audit trails, dispute timelines, and anything legal needs the exact moment one click away.
Time zones
The backend stores everything in UTC. The interface displays in the user's local zone. The friction is at the boundary — exports, scheduled jobs, audit trails, any time the reader and the writer aren't in the same room.
- Display in the user's zoneby default — that's what they expect. Detect from the browser, fall back to the merchant's configured zone, fall back to UTC.
- Label the zone when the audience is mixed (an event time shared with merchants in different countries, a deadline that ends at midnight somewhere). Three-letter caps:
CET,CEST,BST,EST,UTC. - Use UTC in exports and APIs. A CSV that opens in three different cities should read the same in all three. Localisation happens at the rendering layer, not in the data.
- Don't mix forms in one view. A page that shows “14:30 CET” in one row and “14:30” in another asks the reader to do work. Pick one and stay with it.
Date ranges
Yes
- 5–10 May 2026
- 5 May – 10 June 2026
- 5 May 2025 – 10 June 2026
- 09:00–17:00 CET
No
- 5 - 10 May 2026 (hyphen, spaced)
- May 5 – June 10, 2026 (US format in en-GB)
- 5 May 2026 to 10 June 2026 (uses a word; use an en dash)
- 9:00am–5:00pm (12h in product UI)
Use an en dash (–) for ranges, never a hyphen (-) or the word “to”. Drop the duplicate month or year if both ends share it: “5–10 May 2026”, not “5 May 2026 – 10 May 2026”.
Per-locale formats
The runtime renderer picks the format based on the user's locale. Source copy is en-GB. The values below are what each locale ends up rendering for the medium-length date and the time.
| Market | Locale | Date | Time | Week starts |
|---|---|---|---|---|
| United Kingdom | en-GB | 5 May 2026 | 14:30 | Monday |
| Denmark | da-DK | 5. maj 2026 | 14.30 | Monday |
| Finland | fi-FI | 5.5.2026 | 14.30 | Monday |
| Germany | de-DE | 5. Mai 2026 | 14:30 | Monday |
| France | fr-FR | 5 mai 2026 | 14h30 | Monday |
| Italy | it-IT | 5 mag 2026 | 14:30 | Monday |
| Netherlands | nl-NL | 5 mei 2026 | 14:30 | Monday |
| United States | en-US | May 5, 2026 | 2:30 pm | Sunday |
Three things to watch
Finland uses a period as the date separator (5.5.2026), Danish uses a period in the time (14.30), and the US flips date order and clock format. Don't normalise these — they're what users in those markets expect.
In code
Always format through Intl.DateTimeFormat with an explicit locale. Never concatenate strings. Never trust the environment's default locale — make it visible.
ts
// ✓ Pass the user's locale and the format spec
const fmt = new Intl.DateTimeFormat("en-GB", {
day: "numeric",
month: "long",
year: "numeric",
});
fmt.format(new Date("2026-05-05")); // "5 May 2026"
// ✓ For the time, opt explicitly into 24-hour
new Intl.DateTimeFormat("en-GB", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
}).format(new Date()); // "14:30"
// ✗ Don't hand-roll
const month = ["Jan", "Feb", "Mar", ...][d.getMonth()]; // breaks every locale
return `${d.getDate()} ${month} ${d.getFullYear()}`;Related
Page history
1 revision- DocumentedDerek Fidler@derekfidler
First documented version. Default formats, the relative-date scale, time-zone disambiguation, and locale exceptions for the eight markets.