Skip to content

Dynamic theme switching feature#2318

Open
desireddymohithreddy0925 wants to merge 4 commits into
Karanjot786:mainfrom
desireddymohithreddy0925:dynamic-theme-switching-feature
Open

Dynamic theme switching feature#2318
desireddymohithreddy0925 wants to merge 4 commits into
Karanjot786:mainfrom
desireddymohithreddy0925:dynamic-theme-switching-feature

Conversation

@desireddymohithreddy0925

@desireddymohithreddy0925 desireddymohithreddy0925 commented Jul 10, 2026

Copy link
Copy Markdown

Description

This PR introduces the themeStore in @termuijs/tss. Previously, changing the active TSS dictionary at runtime required cumbersome imperative calls that didn't automatically re-render declarative JSX trees smoothly. By wrapping ThemeProvider in a @termuijs/store reactive singleton, users can now swap themes using themeStore.setActiveTheme('dracula') natively!

The store transparently maps named strings to their corresponding ThemeTokens palettes (e.g. catppuccin, tokyo-night) while ensuring the underlying layout engine globally recalculates any component styles relying on CSS-like variables (e.g. var(--bg)).

Related Issue

Closes #2262

Which package(s)?

@termuijs/tss

Type of Change

  • 🐛 Bug fix (type:bug)
  • ✨ Feature (type:feature)
  • 📝 Docs (type:docs)
  • 🧪 Tests (type:testing)
  • ♻️ Refactor (type:refactor)
  • 🎨 Design / UX (type:design)
  • ♿ Accessibility (type:accessibility)
  • ⚡ Performance (type:performance)
  • 🔧 DevOps / CI (type:devops)
  • 🔒 Security (type:security)

Checklist

  • ⭐ You starred the repo. The needs-star check blocks your merge otherwise.
  • Tests pass locally: bun vitest run
  • Build passes: bun run build
  • Typecheck passes: bun run typecheck
  • You read CONTRIBUTING.md.
  • Your PR title follows type: short description.
  • Widget state mutators call markDirty() (if your change affects rendering).
  • No new any types without an inline comment explaining why.
  • No unrelated refactors bundled into this PR.

GSSoC 2026 Participation

  • You are a GSSoC 2026 contributor.
  • Your GSSoC profile: https://gssoc.girlscript.org/profile/YOUR_PROFILE_ID_HERE

Screenshots / Recordings (UI changes)

Notes for the Reviewer

The implementation uses Object.assign() directly on the createStore hook so the user doesn't have to useStore() just to swap the theme; they can do themeStore.setActiveTheme(...) purely procedurally from outside of the React-style render loops.

Summary by CodeRabbit

  • New Features
    • Added a centralized theme store for accessing current theme tokens and the active theme name.
    • Added support for switching themes by name or by providing custom theme tokens.
    • Theme changes now stay synchronized across the application and are available through the library’s public API.

@github-actions github-actions Bot added the area:tss @termuijs/tss label Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a reactive themeStore backed by ThemeProvider, supporting named or token-based theme changes, provider synchronization, and top-level package exports.

Changes

Runtime theme store

Layer / File(s) Summary
Theme store state and synchronization
packages/tss/src/themeStore.ts
Defines ThemeStoreState, initializes theme state from ThemeProvider, synchronizes provider updates, and applies named or token-based themes.
Theme store public API
packages/tss/src/themeStore.ts, packages/tss/src/index.ts
Exports the wrapped themeStore and ThemeStoreState through the package entry point.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant themeStore
  participant ThemeProvider
  Caller->>themeStore: setActiveTheme(themeInput)
  themeStore->>ThemeProvider: resolve and set next theme
  ThemeProvider-->>themeStore: notify theme update
Loading

Possibly related PRs

Suggested labels: type:feature, level:intermediate, quality:clean

Suggested reviewers: Karanjot786

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and directly summarizes the main change: adding dynamic theme switching.
Description check ✅ Passed The PR follows the required template and includes the main sections, linked issue, package, type, checklist, and reviewer notes.
Linked Issues check ✅ Passed The change appears to implement runtime theme switching with a reactive themeStore and ThemeProvider sync, matching #2262's requirements.
Out of Scope Changes check ✅ Passed The diff is narrowly focused on themeStore and index re-exports, with no obvious unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/tss/src/themeStore.ts (1)

18-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

activeThemeName goes stale when ThemeProvider.setTheme() is called externally.

The subscriber only syncs theme, not activeThemeName. If any code calls ThemeProvider.setTheme() directly (bypassing the store), activeThemeName will hold an incorrect value. Consider documenting this limitation or tracking the source of updates so the name stays consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tss/src/themeStore.ts` around lines 18 - 20, Update the
ThemeProvider.subscribe callback in themeStore so externally supplied themes
also keep activeThemeName synchronized with theme, deriving the name from the
new theme or tracking update origin as needed; alternatively document the
limitation if synchronization cannot be guaranteed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/tss/src/themeStore.ts`:
- Around line 36-38: Remove the theme property from the state update in the
theme-switching method, likely within the theme store’s setTheme implementation,
so it only sets activeThemeName before calling
ThemeProvider.setTheme(nextTheme); rely on the ThemeProvider subscriber to
update theme and notify subscribers once.
- Line 24: Initialize activeThemeName consistently with
ThemeProvider.getTheme(): use detectDark() to select defaultDark for dark-mode
systems and defaultLight otherwise, rather than hardcoding defaultDark. Update
the relevant initialization in the theme store while preserving the existing
theme state behavior.

---

Nitpick comments:
In `@packages/tss/src/themeStore.ts`:
- Around line 18-20: Update the ThemeProvider.subscribe callback in themeStore
so externally supplied themes also keep activeThemeName synchronized with theme,
deriving the name from the new theme or tracking update origin as needed;
alternatively document the limitation if synchronization cannot be guaranteed.
🪄 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 Plus

Run ID: fc8fccdb-2e4b-4f39-b4af-845c65a20690

📥 Commits

Reviewing files that changed from the base of the PR and between 65d1f7e and 6a4fc3d.

📒 Files selected for processing (2)
  • packages/tss/src/index.ts
  • packages/tss/src/themeStore.ts


return {
theme: ThemeProvider.getTheme(),
activeThemeName: 'defaultDark',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Initial activeThemeName is hardcoded and may mismatch the actual initial theme.

ThemeProvider.getTheme() returns defaultLight when detectDark() is false, but activeThemeName is always 'defaultDark'. This produces incorrect state on initialization for light-mode systems.

🔧 Proposed fix
+import { defaultDark } from './tokens.js';
 import type { ThemeTokens } from './tokens.js';
@@
-        activeThemeName: 'defaultDark',
+        activeThemeName: ThemeProvider.getTheme() === defaultDark ? 'defaultDark' : 'defaultLight',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
activeThemeName: 'defaultDark',
import { defaultDark } from './tokens.js';
activeThemeName: ThemeProvider.getTheme() === defaultDark ? 'defaultDark' : 'defaultLight',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tss/src/themeStore.ts` at line 24, Initialize activeThemeName
consistently with ThemeProvider.getTheme(): use detectDark() to select
defaultDark for dark-mode systems and defaultLight otherwise, rather than
hardcoding defaultDark. Update the relevant initialization in the theme store
while preserving the existing theme state behavior.

Comment on lines +36 to +38
set({ activeThemeName: name, theme: nextTheme });
// This triggers the global listeners bound in components
ThemeProvider.setTheme(nextTheme);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Redundant double state update causes double re-render.

set({ activeThemeName: name, theme: nextTheme }) updates theme in the store, then ThemeProvider.setTheme(nextTheme) synchronously triggers the subscriber at line 18 which calls set({ theme: newTheme }) again with the same value. All store subscribers are notified twice, triggering duplicate global re-renders for every theme switch.

Remove theme from the set call and let the ThemeProvider subscriber handle it.

♻️ Proposed fix
-            set({ activeThemeName: name, theme: nextTheme });
+            set({ activeThemeName: name });
             // This triggers the global listeners bound in components
             ThemeProvider.setTheme(nextTheme);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
set({ activeThemeName: name, theme: nextTheme });
// This triggers the global listeners bound in components
ThemeProvider.setTheme(nextTheme);
set({ activeThemeName: name });
// This triggers the global listeners bound in components
ThemeProvider.setTheme(nextTheme);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tss/src/themeStore.ts` around lines 36 - 38, Remove the theme
property from the state update in the theme-switching method, likely within the
theme store’s setTheme implementation, so it only sets activeThemeName before
calling ThemeProvider.setTheme(nextTheme); rely on the ThemeProvider subscriber
to update theme and notify subscribers once.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:tss @termuijs/tss

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] Dynamic Theme Switching at Runtime

1 participant