Skip to content

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

Open
mehdibha wants to merge 5 commits into
adobe: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 5 commits into
adobe: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
Copy link
Copy Markdown
Member

@yihuiliao yihuiliao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes make sense. I cleaned up some dead code like removing the array branch in mapSelector and wrapSelector since getSelector no longer returns arrays or functions after this change. I ran the tailwind starter locally and spot checked it but would be good for other to verify as well. Once we get this merged, we can test through the RAC docs

mehdibha added a commit to mehdibha/dotUI that referenced this pull request Jun 4, 2026
…s not-* variants) (#172)

* fix: restore input focus ring by patching tailwindcss-react-aria-components

The plugin emits native state variants (invalid, focus, disabled, …) as two
sibling rules — one for RAC elements ([data-rac][data-invalid]) and one for the
native pseudo-class (:not([data-rac]):invalid). Tailwind's not-* compound walker
bails out on multi-rule variants, so every not-invalid: utility silently emitted
no CSS.

The input field relies on focus:not-invalid:border-border-focus and
focus:not-invalid:ring-border-focus-muted, so focused inputs lost their blue
focus border and ring after the move to the official react-aria packages.

Patch getSelector to collapse the two branches into a single :is() selector,
keeping specificity at (0,1,0) via :where(), so not-* negation works again.
Backports adobe/react-spectrum#9974.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style(input): apply oxfmt class ordering to styles.ts

Pre-existing formatting drift flagged by oxfmt --check; reordering Tailwind
classes in the source string is behavior-neutral (Tailwind decides precedence
by its own internal sort, not class-attribute order). Keeps CI green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants