Skip to content

fix(native): let a rule's shadow list replace an earlier rule's - #488

Open
YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/shadow-list-replaces
Open

YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/shadow-list-replaces

Conversation

@YevheniiKotyrlo

@YevheniiKotyrlo YevheniiKotyrlo commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A compiled box-shadow is written one entry at a time (&.boxShadow.[0].color, &.boxShadow.[0].offsetX, …), and the walk wrote into whatever list the element already held, so a later rule's list merged into an earlier rule's instead of replacing it:

.first { box-shadow: 1px 1px 0 0 #123456, 2px 2px 0 0 #654321; }
.second { box-shadow: 3px 3px 0 0 #13579b; }

className="first second" renders two shadows, #13579b and #654321, where a browser renders #13579b alone. An outset shadow over an inset one keeps inset: true, and the same happens between two !important lists and across specificity. The same CSS resolved at runtime, through a variable that is not inlined, already replaced the list, so the result depended on whether the compiler had inlined the variable.

Solution

applyDeclarations runs once per rule. It now remembers the lists that rule starts, and the rule's first write to any other list replaces it with a new one.

Tests

src/__tests__/native/box-shadow.test.tsx: ten cascades — a list over a longer one, over an inset shadow and over one held in a variable; a list held in a variable over a literal one and over another; none over a list and a list over none; an !important list over an earlier important one and under a later normal one; and a more specific list over a later one — each run with variables inlined and resolved at runtime, and with the sheet injected as compiled and as the JSON a device receives. Each expected value is what Chromium 153, Firefox 155 and WebKit 26.6 compute for the same CSS. On main 22 of the 40 fail: every case that writes a compiled list over a list an earlier rule wrote. The other 18 — a list resolved at runtime, none, and an important list under a later normal one — pass before and after.

Verification

On Ubuntu with the workflow's steps - build, lint, typecheck, yarn test, node --test node/__tests__/*.mjs, both library builds, the web export, the unstaged-changes checks and commitlint - everything passes.

Device evidence, before and after: #488 (comment)

Not run: the iOS and Android example builds.

Merge order

It shares lines with #391 (native/styles/calculate-props.ts), #393 (native/styles/calculate-props.ts), #474 (native/styles/calculate-props.ts); whichever lands second rebases.

A compiled box-shadow is written index by index, and the walk reused the
list an earlier rule had already written for the element. A later rule's
list therefore merged into it: `[green]` over `[red, blue]` rendered
`[green, blue]`, and an outset shadow over an inset one kept `inset: true`.
The same CSS resolved at runtime, through a variable, already replaced the
list. Each rule's first write to a list now starts a new one.
@YevheniiKotyrlo

Copy link
Copy Markdown
Contributor Author

Device evidence — before / after

UNFIXED — Both subjects keep what the earlier rule wrote: the first paints the earlier rule's blue shadow behind the green one, and the second paints its green shadow inset.

FIXED — Every box casts one green shadow outside its bottom-right edge. A later rule's shadow list replaces an earlier rule's, as the control's list held in a variable already does.

before — 3.1.0-rc.0 after — with this PR

Android 16 emulator (API 36, GLES on the host GPU), 1140×2400 @ 480dpi, both frames from one run. before is the published 3.1.0-rc.0 plus what my test app needs from my other patches to run: the native and native-internal subpath exports, PR 459, cascade-layer ranking, and a shim that compiles a class-qualified :root inert. after adds the rest of my open PRs, this one included. One of them shows on every screen: PR 417 narrows a var()-valued font-family to one family, so the after sets its text in the theme's face where the before falls back to the platform's.

@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as ready for review October 16, 2026 10:02

This branch has not been deployed

No deployments
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