From f14f1959bb5b37954a82d6212cb6866737c32151 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 12 Feb 2026 00:48:17 +0100 Subject: [PATCH 1/5] feat: add part="text" to shadow DOM span for external CSS targeting Wrap shadow root content in a so consumers can style the inner element via ::part(text), enabling ::selection and other non-inheritable CSS properties to work across shadow boundaries. Co-Authored-By: Claude Opus 4.6 --- src/relative-time-element.ts | 9 ++++----- test/relative-time.js | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/relative-time-element.ts b/src/relative-time-element.ts index e3612ef..986e514 100644 --- a/src/relative-time-element.ts +++ b/src/relative-time-element.ts @@ -272,14 +272,13 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor } #updateRenderRootContent(content: string | null): void { + const span = document.createElement('span') + span.setAttribute('part', 'text') if (this.hasAttribute('aria-hidden') && this.getAttribute('aria-hidden') === 'true') { - const span = document.createElement('span') span.setAttribute('aria-hidden', 'true') - span.textContent = content - ;(this.#renderRoot as Element).replaceChildren(span) - } else { - this.#renderRoot.textContent = content } + span.textContent = content + ;(this.#renderRoot as Element).replaceChildren(span) } #shouldDisplayUserPreferredAbsoluteTime(format: ResolvedFormat): boolean { diff --git a/test/relative-time.js b/test/relative-time.js index b9c809d..b596d1f 100644 --- a/test/relative-time.js +++ b/test/relative-time.js @@ -2121,7 +2121,6 @@ suite('relative-time', function () { await Promise.resolve() assert.isNull(time.shadowRoot.querySelector('[aria-hidden]'), 'Expected no aria-hidden to be present') - assert.isNull(time.shadowRoot.querySelector('span'), 'Expected no span to be present') }) test('no aria-hidden applies to shadow root', async () => { @@ -2131,7 +2130,18 @@ suite('relative-time', function () { await Promise.resolve() assert.isNull(time.shadowRoot.querySelector('[aria-hidden]'), 'Expected no aria-hidden to be present') - assert.isNull(time.shadowRoot.querySelector('span'), 'Expected no span to be present') + }) + }) + + suite('[part]', () => { + test('shadow root span has part="text"', async () => { + const now = new Date().toISOString() + const time = document.createElement('relative-time') + time.setAttribute('datetime', now) + await Promise.resolve() + + const span = time.shadowRoot.querySelector('span') + assert.equal(span.getAttribute('part'), 'text') }) }) From 441b46f706b756f4663a5f77c3486265e37e6662 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 12 Feb 2026 01:07:02 +0100 Subject: [PATCH 2/5] docs: add part="text" styling section to README and test for aria-hidden coexistence Document the shadow DOM `part="text"` attribute in a new Styling section with a CSS `::part()` usage example. Add test coverage verifying `part="text"` is preserved when `aria-hidden="true"` is also set. Co-Authored-By: Claude Opus 4.6 --- README.md | 11 +++++++++++ test/relative-time.js | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/README.md b/README.md index 5c399f7..ed49f50 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,17 @@ Lang is a [built-in global attribute](https://developer.mozilla.org/en-US/docs/W Adding the `no-title` attribute will remove the `title` attribute from the `` element. The `title` attribute is inaccessible to screen reader and keyboard users, so not adding a title attribute allows a user to create a custom, accessible tooltip if one is desired. +## Styling + +The element renders its text inside a Shadow DOM `` with a [`part="text"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part) attribute. This allows you to style the displayed text from outside the shadow root using the `::part()` CSS pseudo-element: + +```css +relative-time::part(text) { + color: rebeccapurple; + font-weight: bold; +} +``` + ## Browser Support Browsers without native [custom element support][support] require a [polyfill][ce-polyfill]. diff --git a/test/relative-time.js b/test/relative-time.js index b596d1f..8e8f447 100644 --- a/test/relative-time.js +++ b/test/relative-time.js @@ -2143,6 +2143,18 @@ suite('relative-time', function () { const span = time.shadowRoot.querySelector('span') assert.equal(span.getAttribute('part'), 'text') }) + + test('shadow root span has part="text" alongside aria-hidden="true"', async () => { + const now = new Date().toISOString() + const time = document.createElement('relative-time') + time.setAttribute('datetime', now) + time.setAttribute('aria-hidden', 'true') + await Promise.resolve() + + const span = time.shadowRoot.querySelector('span') + assert.equal(span.getAttribute('part'), 'text') + assert.equal(span.getAttribute('aria-hidden'), 'true') + }) }) suite('legacy formats', function () { From 08cd402d9f36a607e1d616bec54e4fec5ef78f19 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 12 Feb 2026 01:09:51 +0100 Subject: [PATCH 3/5] add example --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ed49f50..fed8d04 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,9 @@ relative-time::part(text) { color: rebeccapurple; font-weight: bold; } +relative-time::part(text)::selection { + background: lightgreen; +} ``` ## Browser Support From c82932a4d93ac016c6ccf1468bfada9612f543f1 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 12 Feb 2026 08:07:50 +0100 Subject: [PATCH 4/5] feat: rename part="text" to part="root" Co-Authored-By: Claude Opus 4.6 --- README.md | 6 +++--- src/relative-time-element.ts | 2 +- test/relative-time.js | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index fed8d04..c1d58d6 100644 --- a/README.md +++ b/README.md @@ -286,14 +286,14 @@ Adding the `no-title` attribute will remove the `title` attribute from the `` with a [`part="text"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part) attribute. This allows you to style the displayed text from outside the shadow root using the `::part()` CSS pseudo-element: +The element renders its text inside a Shadow DOM `` with a [`part="root"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part) attribute. This allows you to style the displayed text from outside the shadow root using the `::part()` CSS pseudo-element: ```css -relative-time::part(text) { +relative-time::part(root) { color: rebeccapurple; font-weight: bold; } -relative-time::part(text)::selection { +relative-time::part(root)::selection { background: lightgreen; } ``` diff --git a/src/relative-time-element.ts b/src/relative-time-element.ts index 986e514..897a8b5 100644 --- a/src/relative-time-element.ts +++ b/src/relative-time-element.ts @@ -273,7 +273,7 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor #updateRenderRootContent(content: string | null): void { const span = document.createElement('span') - span.setAttribute('part', 'text') + span.setAttribute('part', 'root') if (this.hasAttribute('aria-hidden') && this.getAttribute('aria-hidden') === 'true') { span.setAttribute('aria-hidden', 'true') } diff --git a/test/relative-time.js b/test/relative-time.js index 8e8f447..a38de41 100644 --- a/test/relative-time.js +++ b/test/relative-time.js @@ -2134,17 +2134,17 @@ suite('relative-time', function () { }) suite('[part]', () => { - test('shadow root span has part="text"', async () => { + test('shadow root span has part="root"', async () => { const now = new Date().toISOString() const time = document.createElement('relative-time') time.setAttribute('datetime', now) await Promise.resolve() const span = time.shadowRoot.querySelector('span') - assert.equal(span.getAttribute('part'), 'text') + assert.equal(span.getAttribute('part'), 'root') }) - test('shadow root span has part="text" alongside aria-hidden="true"', async () => { + test('shadow root span has part="root" alongside aria-hidden="true"', async () => { const now = new Date().toISOString() const time = document.createElement('relative-time') time.setAttribute('datetime', now) @@ -2152,7 +2152,7 @@ suite('relative-time', function () { await Promise.resolve() const span = time.shadowRoot.querySelector('span') - assert.equal(span.getAttribute('part'), 'text') + assert.equal(span.getAttribute('part'), 'root') assert.equal(span.getAttribute('aria-hidden'), 'true') }) }) From e14fff4de8fc7650fa21591ade283d2fe02dce2c Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 12 Feb 2026 08:10:08 +0100 Subject: [PATCH 5/5] update docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1d58d6..5054225 100644 --- a/README.md +++ b/README.md @@ -286,7 +286,7 @@ Adding the `no-title` attribute will remove the `title` attribute from the `` with a [`part="root"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part) attribute. This allows you to style the displayed text from outside the shadow root using the `::part()` CSS pseudo-element: +The element renders its text inside a Shadow DOM `` with a [`part="root"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part) attribute. This allows you to style the element inside the Shadow DOM from outside using the `::part()` CSS pseudo-element: ```css relative-time::part(root) {