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
git clone https://github.com/invivek26/uniwind-platform-variant-leak-repro && cd uniwind-platform-variant-leak-repro
bun install (resolves uniwind 1.12.0 + @tailwindcss/node 4.3.3; the same processor code ships in Pro 1.7.0)
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 〰
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-foregroundon aTextInputrendering 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:rootchild.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. Inpackages/uniwind/src/bundler/css-processor/processor.tstherule.type === 'media'branch pushes the media queries once, then doesthis.declarationConfig = this.getDeclarationConfig()after each child rule, so children 2..N see an emptymediaQueriesand never getplatformset.addMetaToStylesTemplatethen 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):
Happy to open a PR with this plus a
ProcessorBuildertest feeding the hoisted shape if that helps.Steps to Reproduce
git clone https://github.com/invivek26/uniwind-platform-variant-leak-repro && cd uniwind-platform-variant-leak-reprobun install(resolves uniwind 1.12.0 +@tailwindcss/node4.3.3; the same processor code ships in Pro 1.7.0)node repro.mjs— compilesglobal.cssthrough uniwind's owncompileCSSforiosandandroidand lists which platform-variant classes land in each stylesheetActual:
Control: forcing Tailwind back to 4.3.2 with
overrideson 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 〰