Skip to content

Fix CSS variable updates for scoped / non-global themes#498

Closed
sascha-kaleidoscode wants to merge 2 commits intouni-stack:mainfrom
sascha-kaleidoscode:main
Closed

Fix CSS variable updates for scoped / non-global themes#498
sascha-kaleidoscode wants to merge 2 commits intouni-stack:mainfrom
sascha-kaleidoscode:main

Conversation

@sascha-kaleidoscode
Copy link
Copy Markdown

@sascha-kaleidoscode sascha-kaleidoscode commented Apr 16, 2026

Problem

Uniwind.updateCSSVariables(theme, variables) did not reliably trigger UI updates for components rendered under <ScopedTheme> when the updated theme differed from the globally active theme.

On web, runtime variable overrides for non-global themes were also not consistently observable via getWebVariable / useCSSVariable, because resolution depended on document.documentElement and computed styles without consulting per-theme runtime state.

Solution

  • Add a theme-scoped variable notification path so variable updates can target subscribers based on the subtree’s effective theme (scopedTheme when present, otherwise the global theme).
  • On web, resolve getWebVariable against the per-theme runtime map for the effective theme before falling back to getComputedStyle.

Testing

  • Regression coverage for web and native scoped-theme variable updates.

Summary by CodeRabbit

  • New Features

    • CSS variable updates are now theme-scoped: updates for a specific scoped theme only re-render components inside that scope, preventing unrelated rerenders and improving performance.
    • Runtime reads of CSS variables short-circuit to scoped runtime values when available, yielding consistent variable resolution.
  • Tests

    • Added native and web tests covering scoped-theme CSS variable behavior and rerender correctness.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9b859ed8-01d1-45fa-9205-9d4814339390

📥 Commits

Reviewing files that changed from the base of the PR and between b952017 and 84308ef.

📒 Files selected for processing (2)
  • packages/uniwind/src/hoc/withUniwind.native.tsx
  • packages/uniwind/src/hooks/useResolveClassNames.native.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/uniwind/src/hooks/useResolveClassNames.native.ts
  • packages/uniwind/src/hoc/withUniwind.native.tsx

📝 Walkthrough

Walkthrough

Adds theme-scoped CSS variable notification and filtering: a new UniwindListener.notifyVariables(theme) path, a shouldNotifyVariables subscription option, runtime CSS variable lookup method, and changes to subscriptions so variable updates only trigger listeners whose scoped theme matches the updated theme.

Changes

Cohort / File(s) Summary
Listener & Config
packages/uniwind/src/core/listener.ts, packages/uniwind/src/core/config/config.ts, packages/uniwind/src/core/config/config.native.ts
Add notifyVariables(theme) and SubscribeOptions.shouldNotifyVariables; replace conditional variable notify with unconditional notifyVariables(theme); add getRuntimeCSSVariableValue(theme, varName).
Subscriptions in Hooks / HOCs
packages/uniwind/src/components/native/useStyle.ts, packages/uniwind/src/hoc/withUniwind.native.tsx, packages/uniwind/src/hooks/useCSSVariable/useCSSVariable.ts, packages/uniwind/src/hooks/useResolveClassNames.native.ts
Pass shouldNotifyVariables predicate to UniwindListener.subscribe and include effective theme in subscription dependencies to scope variable notifications.
Style Resolution & Cache
packages/uniwind/src/core/native/store.ts, packages/uniwind/src/core/web/getWebStyles.ts
Compute effectiveTheme for cache selection and gate cache invalidation by theme; getWebVariable reads runtime CSS variable via new config API before falling back to computed styles.
Tests & Setup
packages/uniwind/tests/native/components/scoped-theme.test.tsx, packages/uniwind/tests/web/scoped-theme-css-variables.test.tsx, packages/uniwind/tests/setup.native.ts
Add tests validating scoped-theme variable updates only rerender matching scoped components; extend test setup with custom theme.

Sequence Diagram

sequenceDiagram
    participant Component as Component/Hook
    participant Config as UniwindConfig
    participant Listener as UniwindListener
    participant Context as UniwindContext
    participant Callback as Subscription Callback

    Component->>Config: updateCSSVariables(theme='custom', vars)
    Config->>Listener: notifyVariables('custom')
    Listener->>Listener: set notifyingVariablesTheme = 'custom'
    Listener->>Callback: invoke Variables dependency handlers
    Callback->>Context: read scopedTheme / effectiveTheme
    Callback->>Listener: shouldNotifyVariables('custom')?
    alt Theme matches scoped theme
        Listener-->>Callback: true
        Callback->>Component: execute callback (rerender)
    else Theme does not match
        Listener-->>Callback: false
        Callback->>Component: skip execution
    end
    Listener->>Listener: clear notifyingVariablesTheme
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • jpudysz

Poem

🐰 A rabbit's hush in code so neat,
Scoped themes hum in rhythmic beat.
Only those who share the hue
Hear the color whisper true.
Hooray — small hops, big delight! 🎨✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main change: fixing CSS variable updates for scoped and non-global themes, which is the central objective of the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/uniwind/src/hoc/withUniwind.native.tsx`:
- Around line 76-78: The subscription's shouldNotifyVariables closure captures
the current effective theme (uniwindContext.scopedTheme ??
UniwindStore.runtime.currentThemeName) but the effect is only keyed by
dependencySum, so change the effect(s) that call UniwindListener.subscribe
(search for UniwindListener.subscribe, rerender, dependencySum,
shouldNotifyVariables, uniwindContext.scopedTheme,
UniwindStore.runtime.currentThemeName) to also include the effective theme value
in their dependency array (e.g. compute const effectiveTheme =
uniwindContext.scopedTheme ?? UniwindStore.runtime.currentThemeName and add
effectiveTheme to the dependency list) so the subscription is torn down and
recreated whenever the scoped/global theme changes; apply the same change to the
other occurrences noted (lines around the other subscribe calls).

In `@packages/uniwind/src/hooks/useResolveClassNames.native.ts`:
- Around line 22-24: The subscription's shouldNotifyVariables closure captures
the current effective theme (uniwindContext.scopedTheme ??
UniwindStore.runtime.currentThemeName) but the subscribe call only depends on
uniwindState.dependencies, so the listener never re-subscribes when the
effective theme changes; update the subscribe invocation in
useResolveClassNames.native (and the analogous location around line 28) to
include the computed effective theme as part of the dependencies passed to
UniwindListener.subscribe (or otherwise trigger recreate when it changes) so
that recreate is re-registered whenever uniwindContext.scopedTheme or
UniwindStore.runtime.currentThemeName changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24d63485-2ec9-4110-80f4-a7c7ff023634

📥 Commits

Reviewing files that changed from the base of the PR and between dfaa63d and b952017.

📒 Files selected for processing (12)
  • packages/uniwind/src/components/native/useStyle.ts
  • packages/uniwind/src/core/config/config.native.ts
  • packages/uniwind/src/core/config/config.ts
  • packages/uniwind/src/core/listener.ts
  • packages/uniwind/src/core/native/store.ts
  • packages/uniwind/src/core/web/getWebStyles.ts
  • packages/uniwind/src/hoc/withUniwind.native.tsx
  • packages/uniwind/src/hooks/useCSSVariable/useCSSVariable.ts
  • packages/uniwind/src/hooks/useResolveClassNames.native.ts
  • packages/uniwind/tests/native/components/scoped-theme.test.tsx
  • packages/uniwind/tests/setup.native.ts
  • packages/uniwind/tests/web/scoped-theme-css-variables.test.tsx

Comment thread packages/uniwind/src/hoc/withUniwind.native.tsx
Comment thread packages/uniwind/src/hooks/useResolveClassNames.native.ts
@Brentlok
Copy link
Copy Markdown
Contributor

Hi @sascha-kaleidoscode!

Thank you for taking the time to investigate this and for putting together a PR. I really appreciate your effort to improve Uniwind and document the issue with ScopedTheme!

That said, for future reference, please always open an Issue to discuss a bug before jumping into a PR. This helps us align on the problem and the best approach before anyone spends time writing code.

Looking at the proposed solution, this touches on several core pieces of Uniwind's logic. Because these systems are highly interconnected and complex, I prefer to handle modifications to the core architecture myself to ensure edge cases are covered and the broader system remains stable. We've totally missed updateCSSVariables while working on ScopedThemes

I noticed during testing that even with this PR, the basic case on the web is still not functioning correctly.

<ScopedTheme theme="premium">
    <View className="bg-background size-20" />
</ScopedTheme>
<Button
    title="Update"
    onPress={() =>
        Uniwind.updateCSSVariables('dark', {
            '--color-background': '#ff0000',
        })}
/>

Because of the architectural changes required, I'm pretty sure that I would start new PR from scratch. However, your PR description explains the problem perfectly. Could you copy your "Problem" section into a new Issue so we can track the bug properly?

Thanks again for bringing this to my attention and for your support of the project!

@sascha-kaleidoscode
Copy link
Copy Markdown
Author

sascha-kaleidoscode commented Apr 17, 2026

Thanks for taking the time to check the PR and your explanation.
I totally understand that core changes should be handled with care and deep package knowledge.
I've created an issue for the problem here

Maybe it's just a copy/paste mistake, but the case you've described has the wrong theme name in updateCSSVariables. It should be premium, right?

@Brentlok
Copy link
Copy Markdown
Contributor

Maybe it's just a copy/paste mistake, but the case you've described has the wrong theme name in updateCSSVariables. It should be premium, right?

Yup, it should be premium

@Brentlok
Copy link
Copy Markdown
Contributor

#502

@Brentlok Brentlok closed this Apr 21, 2026
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.

2 participants