Runtime theme switching for Unity UI Toolkit. Light/dark/custom themes, token overrides, OS dark-mode auto-follow on Windows + macOS, smooth crossfades, persistence. 3 demos. Zero dependencies.Unity 6 ships TSS (Theme Style Sheets) and USS custom properties as primitives — but no turnkey runtime theme system. There's no C# swap API for driving themes from code; no OS dark-mode detection; every swap is a hard cut with no transition; and no way to override a single token at runtime.Drop a ThemeManager onto your UIDocument GameObject, point it at a ThemeRegistry, and `themeManager.SetTheme("dark")` swaps the entire panel — or call `themeManager.SetToken("--color-accent", Color.red)` to override a single CSS variable at runtime without touching files. The full theme-swap pipeline is awaitable, the pre-change event is cancellable for guard logic, and the override layer persists through theme swaps so user preferences survive.Async swap API that feels native.`await themeManager.SetThemeAsync("dark")` completes when the swap finishes — so your code reads top-to-bottom. Cancellation tokens, `BeforeThemeChange` with a `Cancel` flag for guard logic, `ThemeChanged` for reactive UI, and `ThemeTransitionComplete` for post-swap hooks. Synchronous `SetTheme(id, skipTransition)` for explicit instant swaps. `NextTheme()` / `PreviousTheme()` cycle the registry. `PushTheme("preview")` / `PopTheme()` for settings screens where the user samples a theme then commits or backs out.Three swap strategies, picked per theme.`StyleSheetSwap` (default) adds and removes USS files on the root. `ThemeStyleSheetSwap` assigns `panelSettings.themeStyleSheet` for global, cross-document themes (with a warning when the PanelSettings is shared across UIDocuments). `RootVariableOverride` keeps your single base USS and treats themes as pure token state. Set the strategy field on the ThemeDefinition asset and the right swap path runs automatically.Token overrides with one-line element bindings.`SetToken("--color-accent", Color.red)` stores a runtime override that outlives every subsequent `SetTheme` call. To reflect the value on a specific element, wire it once with `BindColorToken`:```themeManager.BindColorToken(myButton, "--color-accent",(el, c) => el.style.backgroundColor = c);```The binding applies the current value immediately, re-applies on every override or theme change, returns an `IDisposable` for explicit cleanup, and auto-unsubscribes when the element detaches from its panel. `BindFloatToken` ships the same surface for numeric tokens. For multi-token or derived-value scenarios, drop down to the raw `TokenChanged` event.`TryGetToken` reads with override → theme → parent-chain precedence. `ClearTokenOverride` / `ClearAllTokenOverrides` remove. Color and float types both supported.Theme inheritance.Declare a base theme with shared tokens and a dark variant that only overrides what changes. The resolver walks the parent chain (cycle-detected, max depth 16) for every token read. Stop repeating yourself across every theme variant.Lifecycle hooks for every swap.Three events fire in order on every theme swap: `BeforeThemeChange` (cancellable — set `ctx.Cancel = true` to abort), `ThemeChanged` (immediately after stylesheets and overrides apply), and `ThemeTransitionComplete` (after the registered animator finishes, or immediately when no animator is registered). Per-manager transition duration; skip-transition flag on every swap call when you need an explicit instant cut.Persistence built in.`PlayerPrefsThemeStorage` (configurable key) is the default — your user's choice persists across runs without any extra setup. `InMemoryThemeStorage` for no-persist mode. A `Custom` mode and the `IThemeStorage` interface let you wire Cloud Save, a JSON file, or an encrypted backend with a single property assignment.OS dark-mode auto-follow.Windows and macOS providers ship out of the box. `themeManager.SystemProvider = new WindowsThemeSystemProvider()` (or `MacOSThemeSystemProvider`) plus `themeManager.Mode = ThemeMode.Auto` and your panel automatically follows the user's OS-level light/dark preference. Both providers poll for changes (2s default), clean up cleanly when disposed, and degrade to manual mode on unsupported platforms. Implement `IThemeSystemProvider` for iOS, Android, Linux, or any other platform.Animated crossfades via UI Toolkit: Tween Engine.For smooth color crossfades between themes, install UI Toolkit: Tween Engine and enable the integration from Tools > KrookedLilly > Tween Engine Setup (tick the Theme Switcher row and click Apply Changes). The integration registers a token interpolator that runs every time you call `SetTheme` / `SetThemeAsync`, writing per-frame interpolated values into the override store for elements wired via `BindColorToken`. Mid-flight swaps cancel cleanly and redirect smoothly toward the new target — clicking Toggle three times in a row produces a continuous animation, not a stutter. Without Tween Engine, theme swaps complete instantly.Locale-aware root classes.`SetLocale("en", rtl: false)` writes `locale-en` and `locale-rtl` / `locale-ltr` classes to the document root. Author per-locale USS rules with zero code branches: `.locale-ja .demo-title { font-size: 18px; }`, `.locale-rtl .nav-row { flex-direction: row-reverse; }`. Subscribe to Unity Localization's `SelectedLocaleChanged` event and forward the new locale code to `SetLocale` — full recipe in the included docs.Three demo scenes included.Basic Toggle — `Toggle`, `Next`, `Previous` buttons walk three sample themes (light, dark, high-contrast) with a live "Active theme: X" label; install Tween Engine to see the visible crossfade between themes. Palette Token — three RGB sliders drive an override into `--color-accent`; watch the swatch and accent-styled buttons update live as you drag; "Clear Override" restores the theme's accent; "Toggle Theme" proves the override outlives the swap. System Theme — "Follow OS theme" toggle binds the Windows or macOS provider to the manager and flips between `Manual` and `Auto`; readout labels show the detected OS preference and the active theme. All three ship with controller scripts, UXML, USS, sample Light + Dark + High Contrast themes, and a configured DemoPanelSettings (Scale With Screen Size, 1920×1080, Match=0).Full C# source, no DLLs.XML documentation on every public API. Three custom inspectors (manager + registry + theme definition), an editor window for live theme preview against the active manager, and a quick-create that stamps out a Light + Dark + Registry triplet ready to fill in. Works with both Unity's Legacy Input Manager and the new Input System package. Zero external dependencies.Switching- ThemeManager component with deferred-frame initialization; the sibling UIDocument component is hidden under a custom-inspector foldout to prevent the known `MissingReferenceException` on play-mode exit, with runtime guards + domain-reload re-enforcement- Synchronous `SetTheme(id, skipTransition)` and Awaitable `SetThemeAsync(id, CancellationToken)`- `BeforeThemeChange` (cancellable), `ThemeChanged`, `ThemeTransitionComplete` events in order- `NextTheme` / `PreviousTheme` registry cycling- `PushTheme` / `PopTheme` stack semantics- `CurrentTheme`, `RegisteredThemes`, `HasTheme(id)` query APIThemes- ThemeDefinition ScriptableObject — id, display name, description, preview icon, parent (inheritance), scope, strategy, panel TSS, USS list, color tokens, float tokens- ThemeRegistry ScriptableObject with duplicate-id, missing-parent, and empty-slot validation- Parent-chain inheritance walks up to 16 levels with cycle detection- ThemeInheritanceResolver collects all token names across a chain for editor toolingTokens- `SetToken(name, Color)` / `SetToken(name, float)` / `TryGetToken` / `ClearTokenOverride` / `ClearAllTokenOverrides`- `BindColorToken` and `BindFloatToken` extension methods — one call wires a token to an element style, returns an `IDisposable`, auto-cleans up on panel detach- `TokenChanged` event for advanced multi-token or derived-value scenarios- Name normalization handles `--color-accent` and bare `color-accent` interchangeably- Override layer outlives theme swaps; precedence is override → theme → parent chainTransitions- Theme swaps complete instantly by default — `BeforeThemeChange` → `ThemeChanged` → `ThemeTransitionComplete` events fire in order on every swap- Configurable per-manager transition duration (consumed by the animator hook)- Skip-transition flag available on every swap call- Static `ThemeManager.TokenAnimator` hook for registering a per-frame token interpolator. When UI Toolkit: Tween Engine is installed and its integration is enabled, it registers itself as the animator and provides per-frame interpolation of color tokens for elements bound via `BindColorToken`. Mid-flight cancellation and smooth re-direct on rapid swaps.OS theme detection- `IThemeSystemProvider` interface with `GetSystemThemeId()` + `SystemThemeChanged` event- `WindowsThemeSystemProvider` (gated by `UNITY_STANDALONE_WIN`) reads the `AppsUseLightTheme` registry key- `MacOSThemeSystemProvider` (gated by `UNITY_STANDALONE_OSX`) reads `AppleInterfaceStyle` via `/usr/bin/defaults` on a background thread- Both providers poll every 2s by default, expose `IDisposable` for clean shutdown, and dispatch events on the Unity main thread- `themeManager.Mode = ThemeMode.Auto` immediately applies the OS-advertised theme when a provider is presentPersistence- `IThemeStorage` interface for pluggable backends- `PlayerPrefsThemeStorage` default with configurable key- `InMemoryThemeStorage` for `ThemePersistenceMode.None`- `Custom` mode for buyer-supplied backends (Cloud Save, JSON, encrypted)Localization- `SetLocale(code, rtl)` writes `locale-{code}` and `locale-rtl` / `locale-ltr` classes to the document root- Unity Localization recipe documented in the asset's docsEditor- Custom ThemeManager inspector embeds the sibling UIDocument editor in a foldout- Runtime panel applies any registered theme by id, with Previous / Next buttons- ThemeRegistry and ThemeDefinition custom inspectors with one-click validation- Editor window lists registered themes against the active manager and applies any of them in real time- Sibling UIDocument is hidden under a foldout in the custom inspector with runtime HideFlags guards and a domain-reload utility; this combination prevents the known `MissingReferenceException` Unity throws on play-mode exit when a UIDocument is selected- Works cleanly across domain reloads and play-mode transitionsSample themes- Light, Dark, and High Contrast USS files declaring `--color-bg`, `--color-surface`, `--color-fg`, `--color-fg-muted`, `--color-accent`, `--color-accent-fg`, `--focus-ring-color`, `--focus-ring-width`, `--spacing-base`, `--radius`Included demos- Basic Toggle — three buttons cycling Light / Dark / High Contrast with a live active-theme label; visible crossfade when Tween Engine is installed- Palette Token — RGB sliders driving `--color-accent` overrides with swatch + hex readout, override-outlives-swap demonstration, and a clear-override button- System Theme — Follow OS theme toggle wires the platform-appropriate provider and switches between `Manual` and `Auto` modes; manual Light / Dark override buttons; readout labels for platform, OS-reported theme, and active themeCompatibility- Unity 6+ (6000.0 and newer)- UI Toolkit (com.unity.modules.uielements)- Full C# source, no DLLs- XML documentation on all public APIs- Zero external dependenciesAI (Claude Code) was used as a development assistant throughout the package creation process. This includes code generation, architecture design, writing unit tests, documentation authoring, and debugging. All AI-generated code was reviewed, tested, and validated by the developer. The final package is 100% human-supervised C# source code with no AI runtime components.



