Skip to content

Commit 26100df

Browse files
Ignore custom variants with :merge(…) selectors
1 parent e57a2f5 commit 26100df

File tree

3 files changed

+49
-0
lines changed

3 files changed

+49
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Ensure that running the Standalone build does not leave temporary files behind ([#17981](https://github.com/tailwindlabs/tailwindcss/pull/17981))
1818
- Fix `-rotate-*` utilities with arbitrary values ([#18014](https://github.com/tailwindlabs/tailwindcss/pull/18014))
1919
- Upgrade: Change casing of utilities with named values to kebab-case to match updated theme variables ([#18017](https://github.com/tailwindlabs/tailwindcss/pull/18017))
20+
- Ignore custom variants using `:merge(…)` selectors in legacy JS plugins as they would create invalid CSS in v4
2021

2122
### Added
2223

packages/tailwindcss/src/compat/plugin-api.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,44 @@ describe('addVariant', () => {
18301830
}"
18311831
`)
18321832
})
1833+
1834+
test('ignores variants that use :merge(…) and ensures `peer-*` and `group-*` rules work out of the box', async () => {
1835+
let { build } = await compile(
1836+
css`
1837+
@plugin "my-plugin";
1838+
@layer utilities {
1839+
@tailwind utilities;
1840+
}
1841+
`,
1842+
{
1843+
loadModule: async (id, base) => {
1844+
return {
1845+
path: '',
1846+
base,
1847+
module: ({ addVariant }: PluginAPI) => {
1848+
addVariant('optional', '&:optional')
1849+
addVariant('group-optional', ':merge(.group):optional &')
1850+
addVariant('peer-optional', ':merge(.peer):optional ~ &')
1851+
},
1852+
}
1853+
},
1854+
},
1855+
)
1856+
let compiled = build([
1857+
'optional:flex',
1858+
'group-optional:flex',
1859+
'peer-optional:flex',
1860+
'group-optional/foo:flex',
1861+
])
1862+
1863+
expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`
1864+
"@layer utilities {
1865+
.group-optional\\:flex:is(:where(.group):optional *), .group-optional\\/foo\\:flex:is(:where(.group\\/foo):optional *), .peer-optional\\:flex:is(:where(.peer):optional ~ *), .optional\\:flex:optional {
1866+
display: flex;
1867+
}
1868+
}"
1869+
`)
1870+
})
18331871
})
18341872

18351873
describe('matchVariant', () => {

packages/tailwindcss/src/compat/plugin-api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ export function buildPluginApi({
115115
)
116116
}
117117

118+
// Ignore variants emitting v3 `:merge(…)` rules. In v4, the `group-*` and `peer-*` variants
119+
// compound automatically.
120+
if (typeof variant === 'string') {
121+
if (variant.includes(':merge(')) return
122+
} else if (Array.isArray(variant)) {
123+
if (variant.some((v) => v.includes(':merge('))) return
124+
} else if (typeof variant === 'object') {
125+
if (Object.keys(variant).some((v) => v.includes(':merge('))) return
126+
}
127+
118128
// Single selector or multiple parallel selectors
119129
if (typeof variant === 'string' || Array.isArray(variant)) {
120130
designSystem.variants.static(

0 commit comments

Comments
 (0)