Fix extraction of quoted arbitrary variants in v3 (#20374)

## Summary

Fixes #18387.

The v3 default extractor did not capture the complete candidate when a
standard variant preceded an arbitrary selector containing quoted
values, such as
`focus-visible:[&:not([aria-selected="true"])]:bg-red-500`.

The quote-aware pattern allowed quote characters in the standard-variant
portion, causing extraction to begin at the surrounding `class` or
`className` attribute and split the candidate. This change excludes
single and double quotes from that portion of the pattern while
continuing to allow quotes inside arbitrary variants.

Regression tests cover both single- and double-quoted selectors with
preceding `focus-visible` and `hover` variants.

[ci-all]

## Test plan

* `npx jest tests/default-extractor.test.js
tests/arbitrary-variants.test.js --runInBand`
* `npm test -- --runInBand`

Results:

* 86 test suites passed
* 1,060 tests passed
* 1 test marked todo
* 4 snapshots passed

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
Ifeanyi Madu 2026-08-03 14:57:26 +01:00 • committed by GitHub
parent f38be227df
commit ff8b2a0db9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 15 additions and 2 deletions

View file

@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Nothing yet!
### Fixed
- Ensure classes are extracted when a variant precedes an arbitrary variant containing a quoted attribute selector (e.g. `focus-visible:[&:not([aria-selected="true"])]:bg-red-500`) ([#20374](https://github.com/tailwindlabs/tailwindcss/pull/20374))
## [3.4.19] - 2025-12-10

View file

@ -133,7 +133,7 @@ function* buildRegExps(context) {
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]\/[\w_-]+/, separator]),
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]),
regex.pattern([/[^\s`\[\\]+/, separator]),
regex.pattern([/[^\s"'`\[\\]+/, separator]),
]),
]

View file

@ -576,3 +576,14 @@ it.each([
expect(extractions).toContain(value)
}
})
// https://github.com/tailwindlabs/tailwindcss/issues/18387
test('arbitrary not selector with preceding variants', () => {
let extractions = defaultExtractor(`
<div class="focus-visible:[&:not([aria-selected='true'])]:bg-red-500"></div>
<div class='hover:[&:not([aria-selected="true"])]:bg-red-500'></div>
`)
expect(extractions).toContain("focus-visible:[&:not([aria-selected='true'])]:bg-red-500")
expect(extractions).toContain('hover:[&:not([aria-selected="true"])]:bg-red-500')
})