Theme Switcher
An all-in-one popover panel that lets users pick a base color scheme and a primary accent color at runtime, with changes persisted across sessions via localStorage.
Demo
Click the circle button to open the theme panel. Changes apply immediately across the whole demo site.
Current theme
Base: Zinc / Primary: Default
DarkModeToggle
A companion component bundled in the same namespace. It uses the same ThemeService and is already embedded inside ThemeSwitcher's popover header, but can also be placed standalone anywhere in your layout.
Dark mode: off
@using NeoUI.Blazor
<DarkModeToggle />Usage
Minimal usage — drop it anywhere in your layout.
@using NeoUI.Blazor
<ThemeSwitcher />
Typical placement is in a top-bar or sidebar footer alongside
DarkModeToggle:
<header class="flex items-center gap-2">
<ThemeSwitcher />
<DarkModeToggle />
</header>Customisation
Use TriggerClass and PopoverContentClass to tailor the trigger button and panel without subclassing.
Fixed Positioning
By default Strategy is Fixed so the popover correctly escapes any CSS stacking context (e.g. sidebars with overflow: hidden or transform). Switch to Absolute if you embed the switcher in regular document flow where fixed positioning causes layout shifts.
@using NeoUI.Blazor
<ThemeSwitcher Strategy="PositioningStrategy.Absolute" />API Reference
ThemeSwitcher component parameters and their types.
| Prop | Type | Default | Description |
|---|---|---|---|
| TriggerClass | string? | null | Additional CSS classes merged onto the trigger Button. |
| PopoverContentClass | string? | null | Additional CSS classes merged onto the PopoverContent panel. |
| Align | PopoverAlign | End | Popover alignment relative to trigger. |
| Strategy | PositioningStrategy | Fixed | Use Fixed inside transformed/overflow-hidden containers; Absolute otherwise. |
| ZIndex | int | ZIndexLevels.PopoverContent | Z-index for the popover panel. |
ThemeService
Inject ThemeService directly if you need to read or change the theme programmatically without rendering the switcher UI.
| Prop | Type | Default | Description |
|---|---|---|---|
| CurrentBaseColor | BaseColor | — | Currently active base color scheme. |
| CurrentPrimaryColor | PrimaryColor | — | Currently active primary accent color. |
| IsDarkMode | bool | — | Whether dark mode is currently active. |
| OnThemeChanged | event Action | — | Fired whenever the theme changes. |
| InitializeAsync() | Task | — | Reads persisted preferences from localStorage. |
| SetBaseColorAsync(BaseColor) | Task | — | Changes and persists the base color scheme. |
| SetPrimaryColorAsync(PrimaryColor) | Task | — | Changes and persists the primary accent color. |
| SetThemeAsync(bool) | Task | — | Toggles dark mode and persists the preference. |
App.razor Setup
Add the following to the
of App.razor (Blazor Web) or index.html (Blazor WASM):1 — Core stylesheet
The main NeoUI stylesheet provides all component styles and CSS variables.
<link href="_content/NeoUI.Blazor/components.css" rel="stylesheet" />
2 — Base color stylesheets
Each base color scheme is a separate CSS file.
In production include only the ones you actually offer to users — if your app only uses Zinc, ship only zinc.css.
<!-- Base color themes (include only those you need) -->
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/base/zinc.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/base/slate.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/base/gray.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/base/neutral.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/base/stone.css" />
3 — Primary color stylesheets
Same rule applies — only include the accent colors your app exposes. Omitting unused files reduces the CSS bundle size.
<!-- Primary color themes (include only those you need) -->
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/red.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/rose.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/orange.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/amber.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/yellow.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/lime.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/green.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/emerald.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/teal.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/cyan.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/sky.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/blue.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/indigo.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/violet.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/purple.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/fuchsia.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/primary/pink.css" />
Production tip — trim unused theme files
4 — Theme JavaScript
The theme script must load before Blazor initializes to avoid a flash of unstyled content (FOUC).
Place both tags at the end of <head>, after the stylesheets.
<!-- Theme JS — must come before Blazor boots to prevent FOUC -->
<script src="_content/NeoUI.Blazor/js/theme.js"></script>
5 — Service registration
ThemeService is registered automatically when you call
AddNeoUIComponents() in
Program.cs.
// Program.cs
builder.Services.AddNeoUIPrimitives();
builder.Services.AddNeoUIComponents(); // registers ThemeService