Skip to content

Let a nested ThemeProvider own the page's theme and follow mode changes - #142

Merged
brionmario merged 1 commit into
thunder-id:mainfrom
brionmario:fix-nested-theme-providers
Oct 28, 2026
Merged

brionmario merged 1 commit into
thunder-id:mainfrom
brionmario:fix-nested-theme-providers

Conversation

@brionmario

Copy link
Copy Markdown
Member

Purpose

An app that renders its own ThemeProvider inside the one ThunderIDProvider renders, to theme the SDK components with its own design system, loses to the outer one:

  • React: on load, the outer provider writes its CSS variables onto the document root after the inner one does, because parent effects run after child effects. When the application has a Design theme in flow meta, the SDK components show that theme instead of the app's. They switch to the app's theme only once something makes the inner provider write again, such as toggling the color scheme.
  • React and Vue: whenever the outer provider's theme changes, for example when flow meta arrives, it writes over the inner one's variables.
  • React and Vue: mode only seeds the color scheme, so a host app that drives mode from its own color scheme toggle never sees the change.

This showed up in the Admin Console (thunder-id/thunderid#6164). On a reload, the manage-* pages rendered in the application's orange, light Design theme inside the app's dark shell. The app currently works around it.

Approach

  • The innermost ThemeProvider owns the document root. Each provider shares a private "theme root" context (ThemeRootContext in React, THEME_ROOT_KEY in Vue).
    • A nested provider claims the root from the one above it.
    • While a claim stands, the outer provider stops writing its CSS variables and text direction.
    • When the nested provider unmounts and releases its claim, the outer provider writes its own theme again.
    • The public ThemeContext and useTheme don't change.
  • The color scheme follows mode when it changes.
    • React re-seeds the scheme from mode during render when the prop changes, the pattern React recommends for adjusting state when a prop changes.
    • Vue watches mode, re-seeds the scheme and restarts the system/class detection.
  • A provider rendered on its own behaves exactly as before.

Related Issues

  • N/A

Related PRs

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
  • Tests provided. (Add links if there are any)
    • Unit Tests: packages/react/src/contexts/Theme/__tests__/ThemeProvider.nested.test.tsx and packages/vue/src/__tests__/providers/ThemeProvider.nested.test.ts. Both cover keeping the nested theme when the outer theme changes, writing the outer theme again when the nested one unmounts, and following a change of mode. They fail without the fix.
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.
  • Cross-SDK parity. Exactly one of parity/prs-raised or parity/prs-not-needed added.
    • parity/prs-not-needed: the bug comes from web providers sharing the DOM document root, which the iOS, Android and Flutter SDKs don't have. React and Vue are both fixed here; Next.js and Nuxt use them.
    • No sample is affected: a provider rendered on its own behaves as before.

Security checks

  • Followed secure coding standards.
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Every ThemeProvider writes its CSS variables and text direction onto the
document root. An app that renders its own ThemeProvider inside the one
ThunderIDProvider renders lost to it: the outer provider wrote its theme
(the flow meta Design theme) last on load in React, and again whenever
its theme changed in both React and Vue, so the SDK components showed a
different theme from the app until something made the inner one write
again.

The innermost ThemeProvider now owns the document root. A nested
provider claims it from the one above, which stops writing while the
claim stands and writes its own theme again once the nested provider
unmounts.

`mode` also only seeded the color scheme, so a host app that drives it
from its own color scheme toggle never saw the change. The color scheme
now follows `mode` when it changes, in React and Vue.

Signed-off-by: Brion <info@brionmario.com>
@brionmario brionmario added bug Something isn't working parity/prs-not-needed Nothing here reaches another SDK labels Oct 28, 2026
@coderabbitai

coderabbitai Bot commented Oct 28, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@brionmario
brionmario merged commit 7afc4b3 into thunder-id:main Oct 28, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working parity/prs-not-needed Nothing here reaches another SDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants