Skip to content

[Pro 1.7.0 / OSS 1.12.0] ios:/android: variants leak into the other platform's bundle with Tailwind 4.3.3 — only the first class per hoisted @media block keeps its platform #668

Description

@invivek26

What happened?

With Tailwind 4.3.3 (which Pro 1.7.0 / OSS 1.12.0 now pin via @tailwindcss/node), ios: / android: / native: classes leak into the other platform's bundle. Only the first class inside each @media ios { … } / @media android { … } block keeps its platform tag; every other class in the block compiles as an unconditional style and is emitted in both the iOS and Android stylesheets.

In our app (Pro 1.7.0, upgraded from 1.6.0 yesterday) this shows as e.g. ios:underline ios:w-screen-content android:border-b android:border-foreground on a TextInput rendering both the iOS text underline and the Android full-width bottom border on iOS, and the iOS full-width sizing plus text underline on Android. ios:font-… android:font-… pairs are affected the same way. Nothing changed in our class strings; the regression came purely from the Tailwind bump inside uniwind.

Theme blocks (@media (prefers-color-scheme: …)) are unaffected because they contain a single :root child.

Root cause: Tailwind 4.3.3 hoists variants. 4.3.2 emitted .ios\:underline { @media ios { … } } per class; 4.3.3 emits one @media ios { .ios\:w-4 {…} .ios\:underline {…} } block. In packages/uniwind/src/bundler/css-processor/processor.ts the rule.type === 'media' branch pushes the media queries once, then does this.declarationConfig = this.getDeclarationConfig() after each child rule, so children 2..N see an empty mediaQueries and never get platform set. addMetaToStylesTemplate then keeps them for every platform. This is the sibling of #661 / #662 — that fix handled the flattened selector half of the 4.3.3 change; this is the hoisted at-rule half.

Fix (verified against the repro; full native output correct on both platforms):

if (rule.type === 'media') {
    const { mediaQueries } = rule.value.query
    const inherited = [...this.declarationConfig.mediaQueries, ...mediaQueries]

    rule.value.rules.forEach(rule => {
        this.declarationConfig = { ...this.getDeclarationConfig(), mediaQueries: [...inherited] }
        this.parseRuleRec(rule)
    })
    this.declarationConfig = this.getDeclarationConfig()

    return
}

Happy to open a PR with this plus a ProcessorBuilder test feeding the hoisted shape if that helps.

Steps to Reproduce

  1. git clone https://github.com/invivek26/uniwind-platform-variant-leak-repro && cd uniwind-platform-variant-leak-repro
  2. bun install (resolves uniwind 1.12.0 + @tailwindcss/node 4.3.3; the same processor code ships in Pro 1.7.0)
  3. node repro.mjs — compiles global.css through uniwind's own compileCSS for ios and android and lists which platform-variant classes land in each stylesheet

Actual:

== ios bundle stylesheet contains:
  ios:w-4 (platform-tagged=true)
  ios:underline (platform-tagged=false)      <- lost its platform
  android:py-2 (platform-tagged=false)       <- android class shipped to iOS
  native:mt-1 (platform-tagged=true)
  native:mb-2 (platform-tagged=false)
== android bundle stylesheet contains:
  ios:underline (platform-tagged=false)      <- ios class shipped to Android
  android:border-b (platform-tagged=true)
  android:py-2 (platform-tagged=false)
  native:mt-1 (platform-tagged=true)
  native:mb-2 (platform-tagged=false)

Control: forcing Tailwind back to 4.3.2 with overrides on the same uniwind 1.12.0 gives the expected output (every class tagged, no cross-platform entries) — see the README.

Snack or Repository Link (Optional)

https://github.com/invivek26/uniwind-platform-variant-leak-repro

Uniwind version

Pro 1.7.0 (reproduced on OSS 1.12.0 as well — same processor)

React Native Version

0.86.3

Platforms

Android, iOS

Expo

Yes (SDK 57)

Additional information 〰

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions