ThemeSwitcher Component
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 BlazorUI.Components.Theme
<DarkModeToggle />Usage
Minimal usage — drop it anywhere in your layout.
@using BlazorUI.Components.Theme
<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.
<!-- Extra ring on trigger -->
<ThemeSwitcher TriggerClass="rounded-full ring-2 ring-primary ring-offset-2 ring-offset-background" />
<!-- Narrower popover panel -->
<ThemeSwitcher PopoverContentClass="!w-72" />Fixed positioning
By default Strategy is
Fixed so the popover and tooltip
correctly escape any CSS stacking context (e.g. sidebars with overflow: hidden
or transform).
Switch to Absolute if you embed the
switcher in a regular document flow where fixed positioning causes layout shifts.
@using BlazorUI.Primitives.Services
<ThemeSwitcher Strategy="PositioningStrategy.Absolute" />API
| Parameter | Type | Default | Description |
|---|---|---|---|
| TriggerClass | string? | — | Additional CSS classes merged onto the trigger Button |
| PopoverContentClass | string? | — | Additional CSS classes merged onto the PopoverContent panel |
| Strategy | PositioningStrategy | Fixed | Forwarded to both the popover and the tooltip. Use Fixed inside transformed/overflow-hidden containers |
| 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.
| Member | Kind | Description |
|---|---|---|
| CurrentBaseColor | Property | Currently active BaseColor |
| CurrentPrimaryColor | Property | Currently active PrimaryColor |
| IsDarkMode | Property | Whether dark mode is currently active |
| OnThemeChanged | Event | Fired whenever base color, primary color, or dark mode changes |
| InitializeAsync() | Method | Reads persisted preferences from localStorage and applies them |
| SetBaseColorAsync(BaseColor) | Method | Changes and persists the base color scheme |
| SetPrimaryColorAsync(PrimaryColor) | Method | Changes and persists the primary accent color |
| SetThemeAsync(bool isDark) | Method | Toggles dark mode and persists the preference |
@inject ThemeService ThemeService
<!-- Read current values -->
<p>@ThemeService.CurrentBaseColor / @ThemeService.CurrentPrimaryColor</p>
@code {
protected override async Task OnInitializedAsync()
{
await ThemeService.InitializeAsync();
ThemeService.OnThemeChanged += () => InvokeAsync(StateHasChanged);
}
private Task ApplyGreenTheme() =>
ThemeService.SetPrimaryColorAsync(PrimaryColor.Green);
}App.razor Setup
The theme system requires a small amount of boilerplate in your host page.
Add the following to the <head> of
App.razor (Blazor Web) or
index.html (Blazor WASM).
1 — Core stylesheet
The main BlazorUI stylesheet provides all component styles and CSS variables.
<link href="_content/NeoBlazorUI.Components/blazorui.css" rel="stylesheet" />2 — Base color stylesheets
Each base color 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/NeoBlazorUI.Components/css/themes/base/zinc.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/base/slate.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/base/gray.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/base/neutral.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/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/NeoBlazorUI.Components/css/themes/primary/red.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/rose.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/orange.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/amber.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/yellow.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/lime.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/green.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/emerald.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/teal.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/cyan.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/sky.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/blue.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/indigo.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/violet.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/purple.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/fuchsia.css" />
<link rel="stylesheet" href="_content/NeoBlazorUI.Components/css/themes/primary/pink.css" />Production tip — trim unused theme files
4 — Theme JavaScript
The theme script must load before Blazor initialises 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/NeoBlazorUI.Components/js/theme.js"></script>
<script>
window.theme.initialize();
</script>5 — Service registration
ThemeService is registered automatically
when you call AddBlazorUIComponents() in
Program.cs.
// Program.cs
builder.Services.AddBlazorUIPrimitives();
builder.Services.AddBlazorUIComponents(); // registers ThemeService