From c4be9bf575b853b8b8060cd07a3a101f41c99ff2 Mon Sep 17 00:00:00 2001 From: Goldloli <39011904+Goldloli@users.noreply.github.com> Date: Sat, 31 Oct 2026 01:12:59 +0800 Subject: [PATCH 1/3] Use tabular numerals for ticking duration and elapsed formats --- src/relative-time-element.ts | 10 ++++++++++ test/relative-time.js | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/relative-time-element.ts b/src/relative-time-element.ts index 801994d..e2acb36 100644 --- a/src/relative-time-element.ts +++ b/src/relative-time-element.ts @@ -423,6 +423,16 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor } else if (span.hasAttribute('aria-hidden')) { span.removeAttribute('aria-hidden') } + // Tabular numerals keep every digit the same width, so a ticking + // duration/elapsed timer doesn't visibly jump around as it counts (#383). + // Other formats get no inline style; `font-variant-numeric: normal !important` + // on `::part(root)` still overrides this for opt-out. + const tabular = this.format === 'duration' || this.format === 'elapsed' + const fontVariantNumeric = tabular ? 'tabular-nums' : '' + const spanStyle = (span as HTMLElement).style + if (spanStyle.fontVariantNumeric !== fontVariantNumeric) { + spanStyle.fontVariantNumeric = fontVariantNumeric + } if (span.textContent !== content) { span.textContent = content } diff --git a/test/relative-time.js b/test/relative-time.js index 84be196..f06771d 100644 --- a/test/relative-time.js +++ b/test/relative-time.js @@ -64,6 +64,30 @@ suite('relative-time', function () { } }) + test('renders duration and elapsed formats with tabular numerals', async () => { + freezeTime(new Date(5000)) + for (const format of ['duration', 'elapsed']) { + const time = document.createElement('relative-time') + time.setAttribute('format', format) + time.setAttribute('datetime', '1970-01-01T00:00:00.000Z') + await Promise.resolve() + assert.equal( + time.shadowRoot.firstElementChild.style.fontVariantNumeric, + 'tabular-nums', + `${format} keeps each tick's digits the same width`, + ) + time.disconnectedCallback() + } + }) + + test('renders other formats without inline numeric styling', async () => { + const time = document.createElement('relative-time') + time.setAttribute('datetime', '2026-10-30T00:00:00.000Z') + await Promise.resolve() + assert.equal(time.shadowRoot.firstElementChild.style.fontVariantNumeric, '') + time.disconnectedCallback() + }) + test('reschedules future micro datetime updates at the explicit threshold boundary', async () => { const originalSetTimeout = window.setTimeout const delays = [] From 3ad46ef5ed954d1a132fdf63f6d893514ce4cd00 Mon Sep 17 00:00:00 2001 From: Goldloli <39011904+Goldloli@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:04:48 +0800 Subject: [PATCH 2/3] Test that reused span drops tabular-nums after a format change --- test/relative-time.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/relative-time.js b/test/relative-time.js index f06771d..6edb88e 100644 --- a/test/relative-time.js +++ b/test/relative-time.js @@ -88,6 +88,23 @@ suite('relative-time', function () { time.disconnectedCallback() }) + test('clears tabular numerals when the format changes away from duration', async () => { + freezeTime(new Date(5000)) + const time = document.createElement('relative-time') + time.setAttribute('format', 'duration') + time.setAttribute('datetime', '1970-01-01T00:00:00.000Z') + await Promise.resolve() + assert.equal(time.shadowRoot.firstElementChild.style.fontVariantNumeric, 'tabular-nums') + time.setAttribute('format', 'auto') + await Promise.resolve() + assert.equal( + time.shadowRoot.firstElementChild.style.fontVariantNumeric, + '', + 'the reused span does not keep tabular-nums after the format change', + ) + time.disconnectedCallback() + }) + test('reschedules future micro datetime updates at the explicit threshold boundary', async () => { const originalSetTimeout = window.setTimeout const delays = [] From 29c54d263c6d5a190d091db97634a81e2232e70a Mon Sep 17 00:00:00 2001 From: Goldloli <39011904+Goldloli@users.noreply.github.com> Date: Thu, 5 Nov 2026 10:57:46 +0800 Subject: [PATCH 3/3] Document tabular numerals for duration/elapsed and the opt-out --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 8fe8f87..0cb50fe 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,14 @@ The `duration` format will display the time remaining (or elapsed time) from the - `4 hours` - `8 days, 30 minutes, 1 second` +The `duration` and `elapsed` formats render with `font-variant-numeric: tabular-nums`, keeping every digit the same width so a ticking timer doesn't visibly jump around as it counts. To opt out, override the `root` part: + +```css +relative-time::part(root) { + font-variant-numeric: normal !important; +} +``` + ##### time-zone (`string`) The`time-zone` attribute allows you to specify the IANA time zone name (e.g., `America/New_York`, `Europe/London`) used for formatting the date and time.