Skip to content

Commit 3aadba7

Browse files
authored
Move modifier of not-*, has-*, and in-* variant to sub variant (#19100)
This PR fixes an issue where a compound variant with a modifier such as `not-group-hover/name:flex` would not generate anything because the `/name` modifier belongs to the `not` variant, and not the compounded `group-hover` variant. This PR is a **workaround** (and definitely not perfect) by special casing the `not`, `has`, and `in` variants such that their modifiers are moved internally to the sub variant as-if the `/name` existed on `group-hover`. We don't do it for other compound variants such as `group` and `peer` because then `group-peer-focus/name:underline` would result in a breaking change: ```diff - .group-peer-focus\\/name\\:flex:is(:where(.group\\/name):is(:where(.peer):focus ~ *) *) + .group-peer-focus\/name\:flex:is(:where(.group):is(:where(.peer\/name):focus ~ *) *) ``` In case the diff is not clear, the name has moved: <img width="1219" height="78" alt="image" src="https://github.com/user-attachments/assets/dce7bc95-9d93-452d-a275-b3891a05a1a4" /> This is also a limited workaround, because if you need multiple modifiers it won't work. I would've loved to special case this _inside_ the `not`, `has`, and `in` code that handles these variants, but we handle the variants in a depth-first way, so by the time you are handling the `not` variant, the sub variant was already handled... In a perfect world, you can use something like `not-group/name-hover` but then it becomes unambiguous because is `name` the name, is `name-hover`? ## Test plan Added a new test that wouldn't generate anything before this fix. Fixes: #15772
1 parent 0c14df1 commit 3aadba7

File tree

4 files changed

+32
-1
lines changed

4 files changed

+32
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Fixed
1515

1616
- Fix Safari devtools rendering issue due to `color-mix` fallback ([#19069](https://github.com/tailwindlabs/tailwindcss/pull/19069))
17-
- Suppress Lightning CSS warnings about `:deep`, `:slotted` and `:global` ([#19094](https://github.com/tailwindlabs/tailwindcss/pull/19094))
17+
- Suppress Lightning CSS warnings about `:deep`, `:slotted`, and `:global` ([#19094](https://github.com/tailwindlabs/tailwindcss/pull/19094))
1818
- Fix resolving theme keys when starting with the name of another theme key in JS configs and plugins ([#19097](https://github.com/tailwindlabs/tailwindcss/pull/19097))
19+
- Allow named groups in combination with `not-*`, `has-*`, and `in-*` ([#19100](https://github.com/tailwindlabs/tailwindcss/pull/19100))
1920

2021
## [4.1.14] - 2025-10-01
2122

packages/tailwindcss/src/candidate.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,6 +2102,11 @@ const variants = [
21022102
// Handle special `@` variants. These shouldn't be printed as `@-`
21032103
['@xl:', '@xl:'],
21042104
['@[123px]:', '@[123px]:'],
2105+
2106+
// Compound variants that forward modifiers
2107+
['not-group-hover/name:', 'not-group-hover/name:'],
2108+
['has-group-peer-hover/name:', 'has-group-peer-hover/name:'],
2109+
['in-group-peer-hover/name:', 'in-group-peer-hover/name:'],
21052110
]
21062111

21072112
let combinations: [string, string][] = []

packages/tailwindcss/src/candidate.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,13 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia
806806
case 'compound': {
807807
if (value === null) return null
808808

809+
// Forward the modifier of the compound variants to its subVariant.
810+
// This allows for `not-group-hover/name:flex` to work.
811+
if (modifier && (root === 'not' || root === 'has' || root === 'in')) {
812+
value = `${value}/${modifier}`
813+
modifier = null
814+
}
815+
809816
let subVariant = designSystem.parseVariant(value)
810817
if (subVariant === null) return null
811818

packages/tailwindcss/src/variants.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2586,6 +2586,24 @@ test('matchVariant sorts deterministically', async () => {
25862586
}
25872587
})
25882588

2589+
test('move modifier of compound variant to sub-variant if its also a compound variant', async () => {
2590+
expect(
2591+
await run([
2592+
'not-group-focus/name:flex',
2593+
'has-group-focus/name:flex',
2594+
'in-group-focus/name:flex',
2595+
2596+
// Keep the `name` on the `group`, don't move it to the `peer` because
2597+
// that would be a breaking change.
2598+
'group-peer-focus/name:flex',
2599+
]),
2600+
).toMatchInlineSnapshot(`
2601+
".not-group-focus\\/name\\:flex:not(:is(:where(.group\\/name):focus *)), .group-peer-focus\\/name\\:flex:is(:where(.group\\/name):is(:where(.peer):focus ~ *) *), :where(:is(:where(.group\\/name):focus *)) .in-group-focus\\/name\\:flex, .has-group-focus\\/name\\:flex:has(:is(:where(.group\\/name):focus *)) {
2602+
display: flex;
2603+
}"
2604+
`)
2605+
})
2606+
25892607
test.each([
25902608
// These are style rules
25912609
[['.foo'], Compounds.StyleRules],

0 commit comments

Comments
 (0)