Skip to content

Commit e938c58

Browse files
Ensure not-* does not remove :is(…) (#16825)
Resolves #16821 This PR removes a special case in the `not-*` variant compound that removed `:is(…)` if it was the only part of the inversed selector list. While in-theory this makes sense, `:is(…)` accepts a _forgiving_ selector list where as `:not(…)` does not. See the [last point here](https://developer.mozilla.org/en-US/docs/Web/CSS/:not#description). This is an issue specifically in combinations with variants that have selectors that are not supported by all browsers yet, for example `:open`. It seems to be the most expected to simply keep the `:is(…)` here in any case. ## Test plan - Ensured the repro form #16821 now also works in browsers that do not support `:open` (Safari and Firefox at the time of writing this): <img width="484" alt="Screenshot 2025-02-26 at 15 36 22" src="https://github.com/user-attachments/assets/f3391693-895b-4e44-8566-95e2960ec4e3" />
1 parent 124b82b commit e938c58

File tree

3 files changed

+12
-20
lines changed

3 files changed

+12
-20
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- _Experimental_: Add `user-valid` and `user-invalid` variants ([#12370](https://github.com/tailwindlabs/tailwindcss/pull/12370))
1515
- _Experimental_: Add `wrap-anywhere`, `wrap-break-word`, and `wrap-normal` utilities ([#12128](https://github.com/tailwindlabs/tailwindcss/pull/12128))
1616

17+
### Fixed
18+
19+
- Ensure `not-*` does not remove `:is(…)` from variants ([#16825](https://github.com/tailwindlabs/tailwindcss/pull/16825))
20+
1721
## [4.0.9] - 2025-02-25
1822

1923
### Fixed

packages/tailwindcss/src/variants.test.ts

+8-15
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,12 @@ test('target', async () => {
232232
})
233233

234234
test('open', async () => {
235-
expect(await run(['open:flex', 'group-open:flex', 'peer-open:flex'])).toMatchInlineSnapshot(`
236-
".group-open\\:flex:is(:where(.group):is([open], :popover-open, :open) *), .peer-open\\:flex:is(:where(.peer):is([open], :popover-open, :open) ~ *), .open\\:flex:is([open], :popover-open, :open) {
237-
display: flex;
238-
}"
239-
`)
235+
expect(await run(['open:flex', 'group-open:flex', 'peer-open:flex', 'not-open:flex']))
236+
.toMatchInlineSnapshot(`
237+
".not-open\\:flex:not(:is([open], :popover-open, :open)), .group-open\\:flex:is(:where(.group):is([open], :popover-open, :open) *), .peer-open\\:flex:is(:where(.peer):is([open], :popover-open, :open) ~ *), .open\\:flex:is([open], :popover-open, :open) {
238+
display: flex;
239+
}"
240+
`)
240241
expect(await run(['open/foo:flex'])).toEqual('')
241242
})
242243

@@ -1449,15 +1450,7 @@ test('not', async () => {
14491450
],
14501451
),
14511452
).toMatchInlineSnapshot(`
1452-
".not-first\\:flex:not(:first-child), .not-last\\:flex:not(:last-child), .not-only\\:flex:not(:only-child), .not-odd\\:flex:not(:nth-child(odd)), .not-even\\:flex:not(:nth-child(2n)), .not-first-of-type\\:flex:not(:first-of-type), .not-last-of-type\\:flex:not(:last-of-type), .not-only-of-type\\:flex:not(:only-of-type), .not-visited\\:flex:not(:visited), .not-target\\:flex:not(:target) {
1453-
display: flex;
1454-
}
1455-
1456-
.not-open\\:flex:not([open], :popover-open, :open) {
1457-
display: flex;
1458-
}
1459-
1460-
.not-default\\:flex:not(:default), .not-checked\\:flex:not(:checked), .not-indeterminate\\:flex:not(:indeterminate), .not-placeholder-shown\\:flex:not(:placeholder-shown), .not-autofill\\:flex:not(:autofill), .not-optional\\:flex:not(:optional), .not-required\\:flex:not(:required), .not-valid\\:flex:not(:valid), .not-invalid\\:flex:not(:invalid), .not-in-range\\:flex:not(:in-range), .not-out-of-range\\:flex:not(:out-of-range), .not-read-only\\:flex:not(:read-only), .not-empty\\:flex:not(:empty), .not-focus-within\\:flex:not(:focus-within), .not-hover\\:flex:not(:hover) {
1453+
".not-first\\:flex:not(:first-child), .not-last\\:flex:not(:last-child), .not-only\\:flex:not(:only-child), .not-odd\\:flex:not(:nth-child(odd)), .not-even\\:flex:not(:nth-child(2n)), .not-first-of-type\\:flex:not(:first-of-type), .not-last-of-type\\:flex:not(:last-of-type), .not-only-of-type\\:flex:not(:only-of-type), .not-visited\\:flex:not(:visited), .not-target\\:flex:not(:target), .not-open\\:flex:not(:is([open], :popover-open, :open)), .not-default\\:flex:not(:default), .not-checked\\:flex:not(:checked), .not-indeterminate\\:flex:not(:indeterminate), .not-placeholder-shown\\:flex:not(:placeholder-shown), .not-autofill\\:flex:not(:autofill), .not-optional\\:flex:not(:optional), .not-required\\:flex:not(:required), .not-valid\\:flex:not(:valid), .not-invalid\\:flex:not(:invalid), .not-in-range\\:flex:not(:in-range), .not-out-of-range\\:flex:not(:out-of-range), .not-read-only\\:flex:not(:read-only), .not-empty\\:flex:not(:empty), .not-focus-within\\:flex:not(:focus-within), .not-hover\\:flex:not(:hover) {
14611454
display: flex;
14621455
}
14631456
@@ -1467,7 +1460,7 @@ test('not', async () => {
14671460
}
14681461
}
14691462
1470-
.not-focus\\:flex:not(:focus), .not-focus-visible\\:flex:not(:focus-visible), .not-active\\:flex:not(:active), .not-enabled\\:flex:not(:enabled), .not-disabled\\:flex:not(:disabled), .not-inert\\:flex:not([inert], [inert] *), .not-has-checked\\:flex:not(:has(:checked)), .not-aria-selected\\:flex:not([aria-selected="true"]), .not-data-foo\\:flex:not([data-foo]), .not-nth-2\\:flex:not(:nth-child(2)) {
1463+
.not-focus\\:flex:not(:focus), .not-focus-visible\\:flex:not(:focus-visible), .not-active\\:flex:not(:active), .not-enabled\\:flex:not(:enabled), .not-disabled\\:flex:not(:disabled), .not-inert\\:flex:not(:is([inert], [inert] *)), .not-has-checked\\:flex:not(:has(:checked)), .not-aria-selected\\:flex:not([aria-selected="true"]), .not-data-foo\\:flex:not([data-foo]), .not-nth-2\\:flex:not(:nth-child(2)) {
14711464
display: flex;
14721465
}
14731466

packages/tailwindcss/src/variants.ts

-5
Original file line numberDiff line numberDiff line change
@@ -427,11 +427,6 @@ export function createVariants(theme: Theme): Variants {
427427
if (selector.includes('::')) return null
428428

429429
let selectors = segment(selector, ',').map((sel) => {
430-
// Remove unnecessary wrapping &:is(…) to reduce the selector size
431-
if (sel.startsWith('&:is(') && sel.endsWith(')')) {
432-
sel = sel.slice(5, -1)
433-
}
434-
435430
// Replace `&` in target variant with `*`, so variants like `&:hover`
436431
// become `&:not(*:hover)`. The `*` will often be optimized away.
437432
sel = sel.replaceAll('&', '*')

0 commit comments

Comments
 (0)