Prevent not-* from being used with variants with multiple sibling rules (#15689)

Variants like this can't be easily negated by our current system:

```css
@custom-variant dark {
  &.is-dark {
    @slot;
  }
  @media (prefers-color-scheme: dark) {
    @slot;
  }
}
```

Right now it produces the following CSS which is logically incorrect:

```css
.utility-name {
  &:not(.is-dark) {
    /* ... */
  }

  @media not (prefers-color-scheme: dark) {
    /* ... */
  }
}
```

The correct CSS is this which requires moving things around:
```css
.utility-name {
  @media not (prefers-color-scheme: dark) {
    &:not(.is-dark) {
      /* ... */
    }
  }
}
```

We're opting to disable this instead of generating incorrect CSS for
now. I'd like to bring this back in the future for simpler cases in the
future.
This commit is contained in:
Jordan Pittman 2025-01-21 10:38:32 -05:00 • committed by GitHub
parent 4035ab0b76
commit 79f21a8f45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 11 deletions

View file

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure `-outline-offset-*` utilities are suggested in IntelliSense ([#15646](https://github.com/tailwindlabs/tailwindcss/pull/15646))
- Write to `stdout` when `--output` is set to `-` or omitted for `@tailwindcss/cli` ([#15656](https://github.com/tailwindlabs/tailwindcss/pull/15656))
- Write to `stdout` when `--output -` flag is used for `@tailwindcss/cli` ([#15656](https://github.com/tailwindlabs/tailwindcss/pull/15656))
- Prevent `not-*` from being used with variants that have multiple sibling rules ([#15689](https://github.com/tailwindlabs/tailwindcss/pull/15689))
- _Upgrade (experimental)_: Pretty print `--spacing(…)` to prevent ambiguity ([#15596](https://github.com/tailwindlabs/tailwindcss/pull/15596))
### Changed

View file

@ -219,6 +219,15 @@ export function applyVariant(
let result = applyVariant(isolatedNode, variant.variant, variants, depth + 1)
if (result === null) return null
if (variant.root === 'not' && isolatedNode.nodes.length > 1) {
// The `not` variant cannot negate sibling rules / at-rules because these
// are an OR relationship. Doing so would require transforming sibling
// nodes into nesting while negating them. This isn't possible with the
// current implementation of the `not` variant or with how variants are
// applied in general (on a per-node basis).
return null
}
for (let child of isolatedNode.nodes) {
// Only some variants wrap children in rules. For example, the `force`
// variant is a noop on the AST. And the `has` variant modifies the

View file

@ -1644,6 +1644,30 @@ test('not', async () => {
}
}
}
@custom-variant parallel-style-rules {
&:hover {
@slot;
}
&:focus {
@slot;
}
}
@custom-variant parallel-at-rules {
@media foo {
@slot;
}
@media bar {
@slot;
}
}
@custom-variant parallel-mixed-rules {
&:hover {
@slot;
}
@media bar {
@slot;
}
}
@tailwind utilities;
`,
[
@ -1660,23 +1684,27 @@ test('not', async () => {
'not-multiple-media-conditions:flex',
'not-starting:flex',
'not-parallel-style-rules:flex',
'not-parallel-at-rules:flex',
'not-parallel-mixed-rules:flex',
// The following built-in variants don't have not-* versions because
// there is no sensible negative version of them.
// These just don't make sense as not-*
'not-force',
'not-*',
'not-force:flex',
'not-*:flex',
// These contain pseudo-elements
'not-first-letter',
'not-first-line',
'not-marker',
'not-selection',
'not-file',
'not-placeholder',
'not-backdrop',
'not-before',
'not-after',
'not-first-letter:flex',
'not-first-line:flex',
'not-marker:flex',
'not-selection:flex',
'not-file:flex',
'not-placeholder:flex',
'not-backdrop:flex',
'not-before:flex',
'not-after:flex',
// This is not a conditional at rule
'not-starting:flex',