Skip to content

active:/focus:/disabled: variants never override an !important base on native (global important flag) #584

Description

@joernroeder

Summary

When the global Tailwind important flag is enabled (@import "tailwindcss" important;), state variants (active:, focus:, disabled:) no longer override a base declaration of the same property on native. The base value wins permanently while the state is active.

It works correctly on web (the active: pseudo-class wins on specificity), and it works on native without the important flag.

Originally filed as "active:bg-* not applied on native" — narrowed down: the cause is the global important flag interacting with the native resolver's precedence (details below).

Reproduction

Minimal repro: https://github.com/joernroeder/uniwind-bug-report-584

yarn && yarn ios   # or yarn android

Press & hold each box:

Box Classes Expected Actual
A bg-red-500 active:bg-blue-500 turns blue stays red ← bug
B active:bg-green-500 (no base) turns green turns green ✓
C bg-red-500 active:opacity-40 dims dims ✓

The trigger is the global flag in src/global.css:

@import "tailwindcss" important;

B and C show the active: mechanism itself works — the failure is specific to a state variant overriding an !important base of the same property. With the global flag, every utility is !important, so no state variant can override a base color on native. active:opacity-* works only because nothing sets a competing base opacity. Remove important from the import and A turns blue as expected.

Environment

  • uniwind 1.9.0 (also reproduces on 1.8.0)
  • tailwindcss 4.3.0
  • expo 56, react-native 0.85, Hermes — iOS and Android

Root cause

src/core/native/store.ts, the per-property precedence check:

if (
    previousBest
    && (
        previousBest.minWidth > style.minWidth
        || previousBest.complexity > style.complexity
        || previousBest.importantProperties.includes(property)   // ← short-circuits before complexity
    )
) {
    continue
}

className entries resolve left-to-right. The base bg-red-500 (complexity: 0) registers backgroundColor first; with the flag its importantProperties includes backgroundColor. When pressed, active:bg-blue-500 (complexity: 1, since active !== null) is strictly more specific and should win — but importantProperties.includes(property) is OR'd before the complexity comparison, so the check short-circuits to continue and the active value is discarded. Importance is treated as an absolute veto, ignoring specificity.

Proposed fix

Only let an !important previousBest veto an override that is not more specific — gate the important term on complexity:

|| (
    previousBest.importantProperties.includes(property)
    && previousBest.complexity >= style.complexity
)

This matches CSS semantics (a state pseudo-class beats a base rule of equal importance) and is order-independent (the complexity > term handles the reverse class order). The repro ships this as a Yarn patch (.yarn/patches/uniwind-npm-1.9.0-*.patch); with it applied, box A turns blue. Verified:

  • base+active, both !important → active wins on press ✔
  • base+active, neither !important → unchanged (active wins) ✔
  • !important base vs non-important same-complexity later → unchanged (base still vetoes) ✔

Happy to open a PR with this change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpending-releaseFixed, waiting for new version to be releaseduniwind-oss

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions