Theme Switcher

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" />

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

Reconnecting...

Attempting to rejoin the server

Connection Lost

Retrying in seconds

Connection Failed

Failed to rejoin the server.
Please retry or reload the page.

Session Paused

The session has been paused by the server

Resume Failed

Failed to resume the session.
Please reload the page.