Repository navigation
fix: draw no text shadow for text-shadow: none - #495
Open
YevheniiKotyrlo wants to merge 4 commits into
Open
YevheniiKotyrlo wants to merge 4 commits into
YevheniiKotyrlo wants to merge 4 commits into
Conversation
A box-shadow, text-shadow or drop-shadow() whose value is only known at runtime was matched against a fixed list of token orders. A spelling the list missed was dropped, `inset` without a colour was taken for the colour, and an omitted blur, spread or colour was left out where the compiler writes 0 and currentcolor. A colourless drop-shadow() became an empty filter entry, which React Native's processFilter throws on, and a lone length reached React Native as a number, which processBoxShadow throws on. All three now read their tokens through one reader: the lengths adjacent, a colour and `inset` on either side, in the counts lightningcss accepts for each property, with every shadow of a list read and an invalid one rejecting the whole value.
This was referenced Oct 16, 2026
Contributor
Author
YevheniiKotyrlo
marked this pull request as ready for review
October 16, 2026 15:00
…r drop shadow does A written value that is not a shadow is invalid, and browsers ignore the declaration. A variable holding one leaves the declaration invalid at computed-value time, which browsers make unset (css-variables-1 §3.1). The reader can tell neither from the other, so every shadow it rejects sets nothing, and each test row asserts what browsers compute.
YevheniiKotyrlo
marked this pull request as draft
October 16, 2026 22:44
lightningcss hands `text-shadow: none` over unparsed, so it resolves at runtime, where the reader answered `none` with nothing: an earlier rule's text shadow survived where CSS removes it, and a nested Text kept its parent's. React Native has no `none`, so the reader writes a transparent shadow with no offset or blur, which draws nothing. The compiler also marked every runtime-parsed shorthand to resolve after the element's other declarations, which a value that reads no variable does not need; resolved there, an earlier rule's `none` would beat a later rule's shadow. Such a value now resolves in its cascade position.
Resolving a runtime-parsed shorthand in place lost what its delay gives a CSS-wide keyword: the placeholder that replaces an earlier rule's value, so `box-shadow: initial` after `box-shadow: 1px 1px red` kept the shadow. The delay stays. `text-shadow: none`, which lightningcss leaves unparsed, compiles instead to the no-shadow the runtime reader writes for a variable holding `none`, so it keeps its cascade position.
YevheniiKotyrlo
force-pushed
the
fix/text-shadow-none
branch
from
October 16, 2026 23:02
e1f0028 to
18f40b2
Compare
YevheniiKotyrlo
marked this pull request as ready for review
October 16, 2026 23:02
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Problem
text-shadow: nonenever removes a text shadow. lightningcss handsnoneover unparsed, so it resolves at runtime, and the runtime reader answers it with nothing:.shadowed { text-shadow: 1px 1px red } .plain { text-shadow: none }keeps the red shadow onclass="shadowed plain", and a nestedTextwithtext-shadow: nonekeeps its parent's. Chromium 153, Firefox 155 and WebKit 26.6 draw neither.Solution
React Native has no
none, and a nestedTextinherits every shadow prop its own style leaves unset, sononebecomes a transparent shadow with no offset or blur, which draws nothing and outranks the parent's.The compiler writes that for a literal
none: lightningcss leaves it unparsed, and resolved at runtime it would land after a later rule's text shadow, because a runtime-parsed declaration resolves after its element's others. The runtime reader writes the same for a variable holdingnone, and both read it fromNO_TEXT_SHADOWinsrc/utilities/text-shadow.ts.A variable holding something that is not a text shadow still leaves the earlier value in place, where CSS makes the declaration
unset; that holds for every property, so it is not part of this change. Until #474 lands, a variable holdingnonein an earlier rule still beats a later rule's shadow, as anytext-shadow: var()does.Tests
src/__tests__/native/text-shadow-none.test.tsxcoversnonein a later rule, on a child under a shadowed parent and in a variable resolved at runtime, a later rule's shadow over an earliernone, and a second element'snoneafter a shadow was written over the first's. 4 of the 5 fail on #489, and each of three mutations fails its own case: a literalnoneresolved at runtime, a reader that does not knownone, and the offset compiled as one object, which the later shadow then writes into.Merge order
Stacked on #489, whose text-shadow reader this changes, so #489 lands first.
It also shares lines with #389 (
compiler/declarations.ts,utilities/index.ts), #391 (compiler/declarations.ts,native/styles/functions/filters.ts,utilities/index.ts), #392 (native/styles/shorthands/box-shadow.ts), #393 (compiler/declarations.ts), #413 (compiler/declarations.ts,native/styles/shorthands/box-shadow.ts), #417 (compiler/declarations.ts), #420 (compiler/declarations.ts,utilities/index.ts), #491 (compiler/declarations.ts,utilities/index.ts), #492 (compiler/declarations.ts,utilities/index.ts), #494 (compiler/declarations.ts,utilities/index.ts); whichever lands second rebases.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: #495 (comment)