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 Tween Engine to Theme Switcher integration from Tools > KrookedLilly > Setup (then 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.SwitchingThemeManager 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-enforcementSynchronous SetTheme(id, skipTransition) and Awaitable SetThemeAsync(id, CancellationToken)BeforeThemeChange (cancellable), ThemeChanged, ThemeTransitionComplete events in orderNextTheme / PreviousTheme registry cyclingPushTheme / PopTheme stack semanticsCurrentTheme, RegisteredThemes, HasTheme(id) query APIThemesThemeDefinition ScriptableObject — id, display name, description, preview icon, parent (inheritance), scope, strategy, panel TSS, USS list, color tokens, float tokensThemeRegistry ScriptableObject with duplicate-id, missing-parent, and empty-slot validationParent-chain inheritance walks up to 16 levels with cycle detectionThemeInheritanceResolver collects all token names across a chain for editor toolingTokensSetToken(name, Color) / SetToken(name, float) / TryGetToken / ClearTokenOverride / ClearAllTokenOverridesBindColorToken and BindFloatToken extension methods — one call wires a token to an element style, returns an IDisposable, auto-cleans up on panel detachTokenChanged event for advanced multi-token or derived-value scenariosName normalization handles --color-accent and bare color-accent interchangeablyOverride layer outlives theme swaps; precedence is override → theme → parent chainTransitionsTheme swaps complete instantly by default — BeforeThemeChange → ThemeChanged → ThemeTransitionComplete events fire in order on every swapConfigurable per-manager transition duration (consumed by the animator hook)Skip-transition flag available on every swap callStatic 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 detectionIThemeSystemProvider interface with GetSystemThemeId() + SystemThemeChanged eventWindowsThemeSystemProvider (gated by UNITY_STANDALONE_WIN) reads the AppsUseLightTheme registry keyMacOSThemeSystemProvider (gated by UNITY_STANDALONE_OSX) reads AppleInterfaceStyle via /usr/bin/defaults on a background threadBoth providers poll every 2s by default, expose IDisposable for clean shutdown, and dispatch events on the Unity main threadthemeManager.Mode = ThemeMode.Auto immediately applies the OS-advertised theme when a provider is presentPersistenceIThemeStorage interface for pluggable backendsPlayerPrefsThemeStorage default with configurable keyInMemoryThemeStorage for ThemePersistenceMode.NoneCustom mode for buyer-supplied backends (Cloud Save, JSON, encrypted)LocalizationSetLocale(code, rtl) writes locale-{code} and locale-rtl / locale-ltr classes to the document rootUnity Localization recipe documented in the asset's docsEditorCustom ThemeManager inspector embeds the sibling UIDocument editor in a foldoutRuntime panel applies any registered theme by id, with Previous / Next buttonsThemeRegistry and ThemeDefinition custom inspectors with one-click validationEditor window lists registered themes against the active manager and applies any of them in real timeSibling 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 selectedWorks cleanly across domain reloads and play-mode transitionsSample themesLight, 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, --radiusIncluded demosBasic Toggle — three buttons cycling Light / Dark / High Contrast with a live active-theme label; visible crossfade when Tween Engine is installedPalette Token — RGB sliders driving --color-accent overrides with swatch + hex readout, override-outlives-swap demonstration, and a clear-override buttonSystem 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 themeCompatibilityUnity 6+ (6000.0 and newer)UI Toolkit (com.unity.modules.uielements)Full C# source, no DLLsXML documentation on all public APIsZero 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.

