From ff8b2a0db95c7fae78df2d23da07d1caba3eafb0 Mon Sep 17 00:00:00 2001 From: Ifeanyi Madu Date: Mon, 3 Aug 2026 14:57:26 +0100 Subject: [PATCH] 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 --- CHANGELOG.md | 4 +++- src/lib/defaultExtractor.js | 2 +- tests/default-extractor.test.js | 11 +++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3641da5e1..554bad8dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/lib/defaultExtractor.js b/src/lib/defaultExtractor.js index 34407794f..f9e9783e2 100644 --- a/src/lib/defaultExtractor.js +++ b/src/lib/defaultExtractor.js @@ -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]), ]), ] diff --git a/tests/default-extractor.test.js b/tests/default-extractor.test.js index a0b6384ef..9d319bbc7 100644 --- a/tests/default-extractor.test.js +++ b/tests/default-extractor.test.js @@ -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(` +
+
+ `) + + expect(extractions).toContain("focus-visible:[&:not([aria-selected='true'])]:bg-red-500") + expect(extractions).toContain('hover:[&:not([aria-selected="true"])]:bg-red-500') +})