Skip to content

fix(tailwindcss-react-aria-components): make not-* variants compose with native-overlapping states#9974

Open
mehdibha wants to merge 1 commit intoadobe:mainfrom
mehdibha:fix/not-variant-composition
Open

fix(tailwindcss-react-aria-components): make not-* variants compose with native-overlapping states#9974
mehdibha wants to merge 1 commit intoadobe:mainfrom
mehdibha:fix/not-variant-composition

Conversation

@mehdibha
Copy link
Copy Markdown
Contributor

@mehdibha mehdibha commented Apr 24, 2026

Problem

not-disabled:, not-invalid:, not-focus:, not-hover: and other not-* utilities built on native-overlapping RAC variants silently emit no CSS.

Tailwind's not-* walker bails when a variant produces > 1 style rule per path (variants.ts#L470-L471), and the dual-selector (array) shape for these variants emits two siblings.

Fix

Collapse both branches into a single :is():where() keeps specificity at (0,1,0), so cascade is unchanged:

- [`&:where([data-rac])${base}`, `&:where(:not([data-rac]))${native}`]
+ `&:is(:where([data-rac])${base}, :where(:not([data-rac]))${native})`

For hover, emit as a CSS-in-JS object (not an @media {...} string) so Tailwind reports compounds as StyleRules | AtRules, keeping group-hover:/peer-hover: composable.

Known limitation

group-not-hover: / peer-not-hover: still don't compose — same architectural constraint that affects native Tailwind's own group-not-hover:.

Test plan

  • not-* on every native-overlapping variant generates CSS
  • group-hover: / peer-hover: / has-hover: unchanged semantics
  • group-not-disabled:, peer-not-disabled:, has-not-disabled:, not-group-hover: work
  • Prefixed mode (rac-*) unchanged

…ith native-overlapping states

Tailwind's `not-*` compound walker bails when a variant produces more than
one style rule per path. The dual-selector (array) shape used for variants
that overlap native CSS states (hover, focus, disabled, invalid, etc.)
emitted two sibling rules, so `not-disabled:`, `not-invalid:`, `not-focus:`,
`not-hover:`, and similar utilities silently generated no CSS.

Collapse both branches into a single `:is()` selector. `:where()` keeps
specificity at (0,1,0) so cascade behavior matches the previous output.

Hover additionally needs `@media (hover: hover)` to prevent sticky styles
on touch devices. Emitting it as a CSS-in-JS object (instead of a string
starting with `@media`) causes Tailwind to report the variant's compounds
as `StyleRules | AtRules`, which preserves `group-hover:` / `peer-hover:`
composition while still giving `not-hover:` the "1 style rule + 1 at-rule
per path" shape the walker requires.

Test coverage extended with `not-*`, `group-not-*`, `peer-not-*`,
`has-not-*`, `not-group-*`, `peer-hover:`, and `in-*` variants.
@mehdibha mehdibha marked this pull request as ready for review April 24, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant