Theme Switcher
An all-in-one popover panel that lets users pick a base color scheme and a primary accent color at runtime. Enable ShowStyles to expose style variant, radius, font, and menu customisation via a second tab. Changes are persisted across sessions via localStorage.
Demo
Click the circle button to open the theme panel. Colors tab is always shown. Enable ShowStyles to reveal the Styles & Layout tab.
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 |
|---|---|---|---|
| ShowStyles | bool | false | When true, adds a "Styles & Layout" tab exposing style variant, radius, font, and menu options alongside the default Colors tab. |
| 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. |
| CurrentStyleVariant | StyleVariant | — | Currently active visual style variant. |
| CurrentRadiusPreset | RadiusPreset | — | Currently active border radius preset. |
| CurrentFontPreset | FontPreset | — | Currently active font preset. |
| 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. |
| SetStyleVariantAsync(StyleVariant) | Task | — | Changes and persists the visual style variant. |
| SetRadiusPresetAsync(RadiusPreset) | Task | — | Changes and persists the border radius preset. |
| SetFontPresetAsync(FontPreset) | Task | — | Changes and persists the font preset. |
| SetThemeAsync(bool) | Task | — | Toggles dark mode and persists the preference. |
| ApplyPresetAsync(ThemePreset) | Task | — | Applies a named preset across all theme dimensions. |
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" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/base/mist.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/base/mauve.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/base/taupe.css" />
<link rel="stylesheet" href="_content/NeoUI.Blazor/css/themes/base/olive.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