From 2f04b409914bea4b0c7c454d1b4593b7ce995b7c Mon Sep 17 00:00:00 2001 From: snmsoodan Date: Mon, 28 Sep 2026 14:31:14 -0700 Subject: [PATCH 1/2] Ensure `sr-only` hides the caption of a table Firefox applies a ``'s `clip-path` to the inner grid box of the table, whereas Chrome and Safari apply it to the outer table wrapper box. A `
` is rendered in the outer table wrapper box, so it is not clipped by the table itself in Firefox and stays visible. `width`/`height: 1px` plus `overflow: hidden` don't hide it either, because a table can't shrink below the size of its own content. Clip the caption explicitly so that `sr-only` hides it in every engine. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7ee9c5ce-96b9-4d2b-a163-a6e5a4a4eb38 --- packages/tailwindcss/src/utilities.test.ts | 4 ++++ packages/tailwindcss/src/utilities.ts | 4 ++++ packages/tailwindcss/tests/ui.spec.ts | 23 ++++++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 66b051d57961..2b477455ea69 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -19,6 +19,10 @@ test('sr-only', async () => { position: absolute; overflow: hidden; } + + .sr-only > caption { + clip-path: inset(50%); + } " `) expect(await run(['-sr-only', 'sr-only-[var(--value)]', 'sr-only/foo'])).toEqual('') diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 477559d7ea67..9120d304c78f 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -599,6 +599,10 @@ export function createUtilities(theme: Theme) { ['clip-path', 'inset(50%)'], ['white-space', 'nowrap'], ['border-width', '0'], + // Firefox applies the `clip-path` of a `` to its inner grid box, so + // a `
` (which lives in the outer table wrapper box) is not clipped + // and stays visible. Clip the caption itself to cover that case. + () => styleRule('& > caption', [decl('clip-path', 'inset(50%)')]), ]) staticUtility('not-sr-only', [ ['position', 'static'], diff --git a/packages/tailwindcss/tests/ui.spec.ts b/packages/tailwindcss/tests/ui.spec.ts index e5da091d8ce4..eb146e7d05f3 100644 --- a/packages/tailwindcss/tests/ui.spec.ts +++ b/packages/tailwindcss/tests/ui.spec.ts @@ -1513,6 +1513,29 @@ test.skip("::file-selector-button can receive a border with just the 'border' ut ) }) +test('sr-only also hides the caption of a table', async ({ page }) => { + let { getPropertyValue } = await render( + page, + html` + + + + + + + +
+ Caption +
Cell
+ `, + ) + + // Firefox applies the `clip-path` of the `` to its inner grid box, and + // a `
` is rendered in the outer table wrapper box, so the caption is + // not clipped by the table itself and must be clipped explicitly. + expect(await getPropertyValue('#caption', 'clip-path')).toEqual('inset(50%)') +}) + test('composing shadow, inset shadow, ring, and inset ring', async ({ page }) => { let { getPropertyList } = await render( page, From 9f1ff7959e578ef5def1b1d4e4361d5a11b46d2a Mon Sep 17 00:00:00 2001 From: snmsoodan Date: Mon, 28 Sep 2026 14:47:00 -0700 Subject: [PATCH 2/2] Reset the caption clipping in `not-sr-only` `sr-only` clips `> caption` so that a table's caption is hidden in Firefox, where a table's own `clip-path` does not clip it. `not-sr-only` has to undo that, otherwise `sr-only md:not-sr-only` keeps the revealed table's caption clipped. Covered by a new UI test that checks both breakpoints. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7ee9c5ce-96b9-4d2b-a163-a6e5a4a4eb38 --- packages/tailwindcss/src/utilities.test.ts | 4 +++ packages/tailwindcss/src/utilities.ts | 2 ++ packages/tailwindcss/tests/ui.spec.ts | 31 +++++++++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 2b477455ea69..7d8d80e347a3 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -41,6 +41,10 @@ test('not-sr-only', async () => { position: static; overflow: visible; } + + .not-sr-only > caption { + clip-path: none; + } " `) expect(await run(['-not-sr-only', 'not-sr-only-[var(--value)]', 'not-sr-only/foo'])).toEqual('') diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 9120d304c78f..b4a254aa4cd0 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -613,6 +613,8 @@ export function createUtilities(theme: Theme) { ['overflow', 'visible'], ['clip-path', 'none'], ['white-space', 'normal'], + // `sr-only` also clips the caption of a table, so undo that here + () => styleRule('& > caption', [decl('clip-path', 'none')]), ]) /** diff --git a/packages/tailwindcss/tests/ui.spec.ts b/packages/tailwindcss/tests/ui.spec.ts index eb146e7d05f3..5e2629b216b7 100644 --- a/packages/tailwindcss/tests/ui.spec.ts +++ b/packages/tailwindcss/tests/ui.spec.ts @@ -1532,10 +1532,39 @@ test('sr-only also hides the caption of a table', async ({ page }) => { // Firefox applies the `clip-path` of the `` to its inner grid box, and // a `
` is rendered in the outer table wrapper box, so the caption is - // not clipped by the table itself and must be clipped explicitly. + // not clipped by the table itself and must be clipped explicitly. Asserting + // the rule that does it is all that can run here: the bug only reproduces in + // Firefox, which is not available in this environment. expect(await getPropertyValue('#caption', 'clip-path')).toEqual('inset(50%)') }) +test('not-sr-only also unclips the caption of a table', async ({ page }) => { + let { getPropertyValue } = await render( + page, + html` + + + + + + + +
+ Caption +
Cell
+ `, + ) + + // the caption must become visible again together with the table it reveals, + // so the responsive reset has to win over the base `sr-only` rule + await page.setViewportSize({ width: 640, height: 768 }) + expect(await getPropertyValue('#caption', 'clip-path')).toEqual('inset(50%)') + + await page.setViewportSize({ width: 1024, height: 768 }) + expect(await getPropertyValue('#table', 'clip-path')).toEqual('none') + expect(await getPropertyValue('#caption', 'clip-path')).toEqual('none') +}) + test('composing shadow, inset shadow, ring, and inset ring', async ({ page }) => { let { getPropertyList } = await render( page,