Skip to content

Commit fbdb858

Browse files
adamwathanthecrypticace
authored andcommitted
Improve relative precedence of rtl, ltr, forced-colors and dark variants (#12584)
* Reduce specificity of `rtl`, `ltr`, and `dark` variants Reduce specificity of `rtl`, `ltr`, and `dark` variants (when using `darkMode: 'class'`) to make them the same as other variants. This also sorts the LTR/RTL and dark variants later in the variant plugin list to ensure that the reduced specificity doesn't cause them to start "losing" to other variants to keep things as backwards compatible as possible. Resolves a long-standing issue where `darkMode: 'media'` and `darkMode: 'class'` had different specificity, which meant switching your dark mode strategy could break your site. * Update changelog --------- Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
1 parent dae4618 commit fbdb858

15 files changed

+196
-131
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828

2929
- Simplify the `sans` font-family stack ([#11748](https://github.com/tailwindlabs/tailwindcss/pull/11748))
3030
- Disable the tap highlight overlay on iOS ([#12299](https://github.com/tailwindlabs/tailwindcss/pull/12299))
31+
- Improve relative precedence of `rtl`, `ltr`, `forced-colors`, and `dark` variants ([#12584](https://github.com/tailwindlabs/tailwindcss/pull/12584))
3132

3233
## [3.3.7] - 2023-12-18
3334

src/corePlugins.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ export let variantPlugins = {
207207
},
208208

209209
directionVariants: ({ addVariant }) => {
210-
addVariant('ltr', ':is([dir="ltr"] &)')
211-
addVariant('rtl', ':is([dir="rtl"] &)')
210+
addVariant('ltr', ':is(:where([dir="ltr"]) &)')
211+
addVariant('rtl', ':is(:where([dir="rtl"]) &)')
212212
},
213213

214214
reducedMotionVariants: ({ addVariant }) => {
@@ -229,7 +229,7 @@ export let variantPlugins = {
229229
}
230230

231231
if (mode === 'class') {
232-
addVariant('dark', `:is(${className} &)`)
232+
addVariant('dark', `:is(:where(${className}) &)`)
233233
} else if (mode === 'media') {
234234
addVariant('dark', '@media (prefers-color-scheme: dark)')
235235
}

src/lib/setupContextUtils.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -765,14 +765,14 @@ function resolvePlugins(context, root) {
765765
]
766766
let afterVariants = [
767767
variantPlugins['supportsVariants'],
768-
variantPlugins['directionVariants'],
769768
variantPlugins['reducedMotionVariants'],
770769
variantPlugins['prefersContrastVariants'],
771-
variantPlugins['darkVariants'],
772-
variantPlugins['forcedColorsVariants'],
773770
variantPlugins['printVariant'],
774771
variantPlugins['screenVariants'],
775772
variantPlugins['orientationVariants'],
773+
variantPlugins['directionVariants'],
774+
variantPlugins['darkVariants'],
775+
variantPlugins['forcedColorsVariants'],
776776
]
777777

778778
return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]

tests/apply.test.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,14 @@ crosscheck(({ stable, oxide }) => {
216216
text-align: left;
217217
}
218218
}
219-
:is(.dark .apply-dark-variant) {
219+
:is(:where(.dark) .apply-dark-variant) {
220220
text-align: center;
221221
}
222-
:is(.dark .apply-dark-variant:hover) {
222+
:is(:where(.dark) .apply-dark-variant:hover) {
223223
text-align: right;
224224
}
225225
@media (min-width: 1024px) {
226-
:is(.dark .apply-dark-variant) {
226+
:is(:where(.dark) .apply-dark-variant) {
227227
text-align: left;
228228
}
229229
}
@@ -513,14 +513,14 @@ crosscheck(({ stable, oxide }) => {
513513
text-align: left;
514514
}
515515
}
516-
:is(.dark .apply-dark-variant) {
516+
:is(:where(.dark) .apply-dark-variant) {
517517
text-align: center;
518518
}
519-
:is(.dark .apply-dark-variant:hover) {
519+
:is(:where(.dark) .apply-dark-variant:hover) {
520520
text-align: right;
521521
}
522522
@media (min-width: 1024px) {
523-
:is(.dark .apply-dark-variant) {
523+
:is(:where(.dark) .apply-dark-variant) {
524524
text-align: left;
525525
}
526526
}
@@ -2404,18 +2404,18 @@ crosscheck(({ stable, oxide }) => {
24042404

24052405
return run(input, config).then((result) => {
24062406
expect(result.css).toMatchFormattedCss(css`
2407-
:is(.dark .foo)::before,
2408-
:is([dir='rtl'] :is(.dark .bar))::before,
2409-
:is([dir='rtl'] :is(.dark .baz:hover))::before {
2407+
:is(:where(.dark) .foo)::before,
2408+
:is(:where([dir='rtl']) :is(:where(.dark) .bar))::before,
2409+
:is(:where([dir='rtl']) :is(:where(.dark) .baz:hover))::before {
24102410
background-color: #000;
24112411
}
2412-
:is([dir='rtl'] :is(.dark .qux))::file-selector-button:hover {
2412+
:is(:where([dir='rtl']) :is(:where(.dark) .qux))::file-selector-button:hover {
24132413
background-color: #000;
24142414
}
2415-
:is([dir='rtl'] :is(.dark .steve):hover):before {
2415+
:is(:where([dir='rtl']) :is(:where(.dark) .steve):hover):before {
24162416
background-color: #000;
24172417
}
2418-
:is([dir='rtl'] :is(.dark .bob))::file-selector-button:hover {
2418+
:is(:where([dir='rtl']) :is(:where(.dark) .bob))::file-selector-button:hover {
24192419
background-color: #000;
24202420
}
24212421
:has([dir='rtl'] .foo:hover):before {

tests/custom-separator.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,22 @@ crosscheck(() => {
2323
.group:hover .group-hover_focus-within_text-left:focus-within {
2424
text-align: left;
2525
}
26-
:is([dir='rtl'] .rtl_active_text-center:active) {
27-
text-align: center;
28-
}
2926
@media (prefers-reduced-motion: no-preference) {
3027
.motion-safe_hover_text-center:hover {
3128
text-align: center;
3229
}
3330
}
34-
:is(.dark .dark_focus_text-left:focus) {
35-
text-align: left;
36-
}
3731
@media (min-width: 768px) {
3832
.md_hover_text-right:hover {
3933
text-align: right;
4034
}
4135
}
36+
:is(:where([dir='rtl']) .rtl_active_text-center:active) {
37+
text-align: center;
38+
}
39+
:is(:where(.dark) .dark_focus_text-left:focus) {
40+
text-align: left;
41+
}
4242
`)
4343
})
4444
})

tests/dark-mode.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ crosscheck(() => {
1717
return run(input, config).then((result) => {
1818
expect(result.css).toMatchFormattedCss(css`
1919
${defaults}
20-
:is(.dark .dark\:font-bold) {
20+
:is(:where(.dark) .dark\:font-bold) {
2121
font-weight: 700;
2222
}
2323
`)
@@ -40,7 +40,7 @@ crosscheck(() => {
4040
return run(input, config).then((result) => {
4141
expect(result.css).toMatchFormattedCss(css`
4242
${defaults}
43-
:is(.test-dark .dark\:font-bold) {
43+
:is(:where(.test-dark) .dark\:font-bold) {
4444
font-weight: 700;
4545
}
4646
`)

tests/format-variant-selector.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,8 @@ crosscheck(() => {
348348
${'.parent::before &:hover'} | ${'.parent &:hover::before'}
349349
${':where(&::before) :is(h1, h2, h3, h4)'} | ${':where(&) :is(h1, h2, h3, h4)::before'}
350350
${':where(&::file-selector-button) :is(h1, h2, h3, h4)'} | ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'}
351-
${'#app :is(.dark &::before)'} | ${'#app :is(.dark &)::before'}
352-
${'#app :is(:is(.dark &)::before)'} | ${'#app :is(:is(.dark &))::before'}
351+
${'#app :is(:where(.dark) &::before)'} | ${'#app :is(:where(.dark) &)::before'}
352+
${'#app :is(:is(:where(.dark) &)::before)'} | ${'#app :is(:is(:where(.dark) &))::before'}
353353
${'#app :is(.foo::file-selector-button)'} | ${'#app :is(.foo)::file-selector-button'}
354354
${'#app :is(.foo::-webkit-progress-bar)'} | ${'#app :is(.foo)::-webkit-progress-bar'}
355355
${'.parent::marker li'} | ${'.parent li::marker'}

tests/important-boolean.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,22 +138,22 @@ crosscheck(() => {
138138
.group:hover .group-hover\:focus-within\:text-left:focus-within {
139139
text-align: left !important;
140140
}
141-
:is([dir='rtl'] .rtl\:active\:text-center:active) {
142-
text-align: center !important;
143-
}
144141
@media (prefers-reduced-motion: no-preference) {
145142
.motion-safe\:hover\:text-center:hover {
146143
text-align: center !important;
147144
}
148145
}
149-
:is(.dark .dark\:focus\:text-left:focus) {
150-
text-align: left !important;
151-
}
152146
@media (min-width: 768px) {
153147
.md\:hover\:text-right:hover {
154148
text-align: right !important;
155149
}
156150
}
151+
:is(:where([dir='rtl']) .rtl\:active\:text-center:active) {
152+
text-align: center !important;
153+
}
154+
:is(:where(.dark) .dark\:focus\:text-left:focus) {
155+
text-align: left !important;
156+
}
157157
`)
158158
})
159159
})

tests/important-selector.test.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -136,29 +136,29 @@ crosscheck(({ stable, oxide }) => {
136136
#app :is(.group:hover .group-hover\:focus-within\:text-left:focus-within) {
137137
text-align: left;
138138
}
139-
#app :is(:is([dir='rtl'] .rtl\:active\:text-center:active)) {
140-
text-align: center;
141-
}
142139
@media (prefers-reduced-motion: no-preference) {
143140
#app :is(.motion-safe\:hover\:text-center:hover) {
144141
text-align: center;
145142
}
146143
}
147-
#app :is(.dark .dark\:before\:underline):before {
148-
content: var(--tw-content);
149-
text-decoration-line: underline;
150-
}
151-
#app :is(:is(.dark .dark\:focus\:text-left:focus)) {
152-
text-align: left;
153-
}
154144
@media (min-width: 768px) {
155145
#app :is(.md\:hover\:text-right:hover) {
156146
text-align: right;
157147
}
158148
}
149+
#app :is(:is(:where([dir='rtl']) .rtl\:active\:text-center:active)) {
150+
text-align: center;
151+
}
152+
#app :is(:where(.dark) .dark\:before\:underline):before {
153+
content: var(--tw-content);
154+
text-decoration-line: underline;
155+
}
156+
#app :is(:is(:where(.dark) .dark\:focus\:text-left:focus)) {
157+
text-align: left;
158+
}
159159
#app
160160
:is(
161-
[dir='rtl'] :is(.dark .hover\:\[\&\:\:file-selector-button\]\:rtl\:dark\:bg-black\/100)
161+
:where([dir='rtl']) :is(:where(.dark) .hover\:\[\&\:\:file-selector-button\]\:rtl\:dark\:bg-black\/100)
162162
)::file-selector-button:hover {
163163
background-color: #000;
164164
}
@@ -187,15 +187,15 @@ crosscheck(({ stable, oxide }) => {
187187
return run(input, config).then((result) => {
188188
stable.expect(result.css).toMatchFormattedCss(css`
189189
${defaults}
190-
#app :is(.dark .dark\:before\:bg-black)::before {
190+
#app :is(:where(.dark) .dark\:before\:bg-black)::before {
191191
content: var(--tw-content);
192192
--tw-bg-opacity: 1;
193193
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
194194
}
195195
`)
196196
oxide.expect(result.css).toMatchFormattedCss(css`
197197
${defaults}
198-
#app :is(.dark .dark\:before\:bg-black)::before {
198+
#app :is(:where(.dark) .dark\:before\:bg-black)::before {
199199
content: var(--tw-content);
200200
background-color: #000;
201201
}

tests/kitchen-sink.test.js

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ crosscheck(({ stable, oxide }) => {
305305
}
306306
.drop-empty-rules:hover,
307307
.group:hover .apply-group,
308-
:is(.dark .apply-dark-mode) {
308+
:is(:where(.dark) .apply-dark-mode) {
309309
font-weight: 700;
310310
}
311311
.apply-with-existing:hover {
@@ -340,7 +340,7 @@ crosscheck(({ stable, oxide }) => {
340340
.apply-order-b {
341341
margin: 1.5rem 1.25rem 1.25rem;
342342
}
343-
:is(.dark .group:hover .apply-dark-group-example-a) {
343+
:is(:where(.dark) .group:hover .apply-dark-group-example-a) {
344344
--tw-bg-opacity: 1;
345345
background-color: rgb(34 197 94 / var(--tw-bg-opacity));
346346
}
@@ -741,9 +741,6 @@ crosscheck(({ stable, oxide }) => {
741741
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
742742
}
743743
}
744-
:is(.dark .dark\:custom-util) {
745-
background: #abcdef;
746-
}
747744
@media (min-width: 640px) {
748745
.sm\:text-center {
749746
text-align: center;
@@ -788,9 +785,6 @@ crosscheck(({ stable, oxide }) => {
788785
transition-duration: 0.15s;
789786
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
790787
}
791-
:is(.dark .md\:dark\:motion-safe\:foo\:active\:custom-util:active) {
792-
background: #abcdef !important;
793-
}
794788
}
795789
@media (min-width: 640px) {
796790
.md\:sm\:text-center {
@@ -808,6 +802,16 @@ crosscheck(({ stable, oxide }) => {
808802
text-align: left;
809803
}
810804
}
805+
:is(:where(.dark) .dark\:custom-util) {
806+
background: #abcdef;
807+
}
808+
@media (min-width: 768px) {
809+
@media (prefers-reduced-motion: no-preference) {
810+
:is(:where(.dark) .md\:dark\:motion-safe\:foo\:active\:custom-util:active) {
811+
background: #abcdef !important;
812+
}
813+
}
814+
}
811815
`)
812816
oxide.expect(result.css).toMatchFormattedCss(css`
813817
.theme-test {
@@ -874,7 +878,7 @@ crosscheck(({ stable, oxide }) => {
874878
}
875879
.drop-empty-rules:hover,
876880
.group:hover .apply-group,
877-
:is(.dark .apply-dark-mode) {
881+
:is(:where(.dark) .apply-dark-mode) {
878882
font-weight: 700;
879883
}
880884
.apply-with-existing:hover {
@@ -908,7 +912,7 @@ crosscheck(({ stable, oxide }) => {
908912
.apply-order-b {
909913
margin: 1.5rem 1.25rem 1.25rem;
910914
}
911-
:is(.dark .group:hover .apply-dark-group-example-a) {
915+
:is(:where(.dark) .group:hover .apply-dark-group-example-a) {
912916
background-color: #22c55e;
913917
}
914918
@media (min-width: 640px) {
@@ -1299,9 +1303,6 @@ crosscheck(({ stable, oxide }) => {
12991303
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
13001304
}
13011305
}
1302-
:is(.dark .dark\:custom-util) {
1303-
background: #abcdef;
1304-
}
13051306
@media (min-width: 640px) {
13061307
.sm\:text-center {
13071308
text-align: center;
@@ -1346,9 +1347,6 @@ crosscheck(({ stable, oxide }) => {
13461347
transition-duration: 0.15s;
13471348
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
13481349
}
1349-
:is(.dark .md\:dark\:motion-safe\:foo\:active\:custom-util:active) {
1350-
background: #abcdef !important;
1351-
}
13521350
}
13531351
@media (min-width: 640px) {
13541352
.md\:sm\:text-center {
@@ -1366,6 +1364,15 @@ crosscheck(({ stable, oxide }) => {
13661364
text-align: left;
13671365
}
13681366
}
1367+
:is(:where(.dark) .dark\:custom-util) {
1368+
background: #abcdef;
1369+
}
1370+
@media (min-width: 768px) {
1371+
@media (prefers-reduced-motion: no-preference) {
1372+
:is(:where(.dark) .md\:dark\:motion-safe\:foo\:active\:custom-util:active) {
1373+
background: #abcdef !important;
1374+
}
1375+
}
13691376
`)
13701377
})
13711378
})

0 commit comments

Comments
 (0)