* fix: read variant tokens from flattened class selectors
Tailwind 4.3.3 stopped nesting variants under the utility class and emits
compound selectors instead:
<= 4.3.2 4.3.3
.active\:x { &:active { ... } } .active\:x:active { ... }
The native processor only read `:active`, `:focus`, `:disabled`, theme
`:where(...)`, `:dir()` and `[data-*]` tokens from nested rules. On the
flattened form the leading class token was accepted and the rest of the
selector ignored, so every variant compiled as an unconditional style:
`active:bg-red-500` was red at rest, `disabled:opacity-50` always dim,
`dark:` always on.
Read variant tokens from the components that follow the class token too,
sharing one reader with the nested path. A flattened compound the runtime
cannot observe (`disabled:` also emits `[aria-disabled="true"]`) is
dropped, matching what its empty nested rule compiled to before.
Regression test feeds both selector shapes straight to ProcessorBuilder,
so it does not depend on which Tailwind the repo pins. With
`@tailwindcss/node` at 4.3.3 the existing suite goes from 11 failures
(pressable, touchables, data-attributes, dir) to green.
* chore: bump tailwind dependencies to 4.3.3
Root catalog, @tailwindcss/node and @tailwindcss/oxide in the package, and
@tailwindcss/vite in the vite example. The repo now runs its own suite
against the flattened variant selectors that 4.3.3 emits.
* fix: handle flattened theme roots on web and skip unobservable compounds
Two follow-ups from review and CI on Tailwind 4.3.3.
Web: Tailwind now flattens `:root { &:where(.dark, .dark *) {} }` into a
sibling `:root:where(.dark, .dark *) {}` rule. The rule visitor only
rewrote nested theme variants into `.dark`, so the flattened form kept its
`:root` prefix and a scoped `.dark` class could not select it; the
`bg-background in dark theme` e2e test failed. Route a theme-layer `:root`
rule whose second token is `:where(.theme)` through the same rewrite.
Native: a selector mixing a supported variant with a token the runtime
cannot observe (`disabled:active:` emits `[aria-disabled="true"]:active`)
used to keep the branch gated on `:active` alone. Track unsupported tokens
in `readSelectorVariants` and skip the whole selector instead of applying
it under a weaker condition.
* feat: add filter support on iOS and React Native canary
* chore: use set instead of array
---------
Co-authored-by: Hubert Bieszczad <brentlokk@gmail.com>
* feat: add ScopedVariables component for per-subtree CSS variable overrides
* feat: add opt-in cacheKey to ScopedVariables for native style caching
* docs(expo-example): add ScopedVariables demo
Demonstrate per-subtree CSS variable overrides in the expo example app:
theme default, scoped override, nested inheritance (nearest wins), and the
opt-in cacheKey. Adds --color-primary/--color-surface/--gap theme defaults so
the unscoped baseline renders intentionally.
* fix(ScopedVariables): apply scoped variables to the web DOM cascade
On web, Uniwind passes classes through RNW unchanged, so real elements
resolve `var(--name)` from the live CSS cascade. The wrapper only applied
its variables to the hidden dummyParent used for JS reads, so scoped
overrides never reached descendants — styling stayed at the theme default
while useCSSVariable readouts (which use the dummyParent path) looked
correct. Set the variables as inline custom properties on the
display:contents wrapper so they cascade to children (numbers -> px).
Add web regression tests asserting the wrapper carries the overrides
inline, nested wrappers only declare their own overrides, and invalid
keys are dropped. Rework the expo-example demo with origin pills
(default/set here/inherited), swatches, and a gap strip so the
override/inherit story is legible.
* fix(ScopedVariables): address review feedback on PR #611
- useCSSVariable: recompute on context change, not only on global
Theme/Variables events — an updated <ScopedVariables> variables prop
(or a nearer provider) now surfaces the new value. Adds web + native
regression tests for a prop update.
- utils: always drop non-`--` keys (not just in dev), gating only the
Logger.error behind __DEV__, so invalid keys can't reach the web read
helper and corrupt a resolved inheritable property in production.
- getWebStyles/getWebVariable: wrap the scoped-variable read in
try/finally so the temporary custom properties are always cleared,
even if a DOM read throws.
- Document the variables prop stability contract (define outside render
or useMemo) in JSDoc.
- Remove two unused @ts-expect-error directives in tests.
- Reword the demo's cacheKey section so it no longer claims parity with
section 2.
* refactor(ScopedVariables): address maintainer review on PR #611
- Derive the native style cache key from the merged variables map instead
of a user-supplied cacheKey prop; removes the cache-bypass path and the
stale-key footgun
- Drop the display: contents View wrapper on native, render the bare
provider like ScopedTheme.native
- Remove the expo-example demo (playground only, covered by tests)
- useCSSVariable: skip the mount-time recompute, useState already
resolved the initial value
- Extract toWebValue (number -> px) web util, reuse in config,
getWebStyles and the web wrapper
- Remove the ScopedVariables prop type test
- Trim long comments to match repo style
* fix(ScopedVariables): use JSON.stringify for the derived cache key
The key:value; concatenation had no escaping, so values containing
separators could collide ({'--a': '1;--b:2'} vs {'--a': '1', '--b': '2'})
and serve wrong cached styles.
* chore: general corrections
* chore: stream ui precommit
* chore: more improvements
* feat: add ScopedVariables component for per-subtree CSS variable overrides
* feat: add opt-in cacheKey to ScopedVariables for native style caching
* docs(expo-example): add ScopedVariables demo
Demonstrate per-subtree CSS variable overrides in the expo example app:
theme default, scoped override, nested inheritance (nearest wins), and the
opt-in cacheKey. Adds --color-primary/--color-surface/--gap theme defaults so
the unscoped baseline renders intentionally.
* fix(ScopedVariables): apply scoped variables to the web DOM cascade
On web, Uniwind passes classes through RNW unchanged, so real elements
resolve `var(--name)` from the live CSS cascade. The wrapper only applied
its variables to the hidden dummyParent used for JS reads, so scoped
overrides never reached descendants — styling stayed at the theme default
while useCSSVariable readouts (which use the dummyParent path) looked
correct. Set the variables as inline custom properties on the
display:contents wrapper so they cascade to children (numbers -> px).
Add web regression tests asserting the wrapper carries the overrides
inline, nested wrappers only declare their own overrides, and invalid
keys are dropped. Rework the expo-example demo with origin pills
(default/set here/inherited), swatches, and a gap strip so the
override/inherit story is legible.
* fix(ScopedVariables): address review feedback on PR #611
- useCSSVariable: recompute on context change, not only on global
Theme/Variables events — an updated <ScopedVariables> variables prop
(or a nearer provider) now surfaces the new value. Adds web + native
regression tests for a prop update.
- utils: always drop non-`--` keys (not just in dev), gating only the
Logger.error behind __DEV__, so invalid keys can't reach the web read
helper and corrupt a resolved inheritable property in production.
- getWebStyles/getWebVariable: wrap the scoped-variable read in
try/finally so the temporary custom properties are always cleared,
even if a DOM read throws.
- Document the variables prop stability contract (define outside render
or useMemo) in JSDoc.
- Remove two unused @ts-expect-error directives in tests.
- Reword the demo's cacheKey section so it no longer claims parity with
section 2.
* refactor(ScopedVariables): address maintainer review on PR #611
- Derive the native style cache key from the merged variables map instead
of a user-supplied cacheKey prop; removes the cache-bypass path and the
stale-key footgun
- Drop the display: contents View wrapper on native, render the bare
provider like ScopedTheme.native
- Remove the expo-example demo (playground only, covered by tests)
- useCSSVariable: skip the mount-time recompute, useState already
resolved the initial value
- Extract toWebValue (number -> px) web util, reuse in config,
getWebStyles and the web wrapper
- Remove the ScopedVariables prop type test
- Trim long comments to match repo style
* fix(ScopedVariables): use JSON.stringify for the derived cache key
The key:value; concatenation had no escaping, so values containing
separators could collide ({'--a': '1;--b:2'} vs {'--a': '1', '--b': '2'})
and serve wrong cached styles.
* chore: general corrections
* chore: stream ui precommit
* chore: more improvements
* chore: remove unused from test
---------
Co-authored-by: Dima Lebedynskyi <dimalebe@amazon.com>
Co-authored-by: Hubert Bieszczad <brentlokk@gmail.com>