ThemeToggle
ThemeToggle renders a round button with a sun or moon icon that switches the site between the light and the dark
theme. The switch happens in the browser, so it works on statically rendered pages, and the choice is kept in a cookie
for the next visit. Use it in your header or menu, together with
OsirionStyles in the layout. The demo below changes this site's own mode, which is the
point of the component, and your choice stays in your browser.
Package Osirion.Blazor.Theming, namespace Osirion.Blazor.Theming.Components. Source on GitHub.
@using Osirion.Blazor.Theming.Components
@* Changes the mode of the page you are reading; the choice is kept in your browser's cookie. *@
<div style="display: flex; align-items: center; gap: 0.75rem;">
<ThemeToggle />
<span>Select the button to switch this site between light and dark.</span>
</div>Usage
@using Osirion.Blazor.Theming.Components
<ThemeToggle />
In a navigation menu, after the links:
@using Osirion.Blazor.Navigation.Components
@using Osirion.Blazor.Theming.Components
<Menu AriaLabel="Main navigation" BrandText="My site" Alignment="MenuAlignment.Right">
<MenuItem Text="Home" Href="/" Icon="home" />
<MenuItem Text="Blog" Href="/blog" Icon="blog" />
<MenuDivider />
<ThemeToggle />
</Menu>
The toggle needs theming registered: AddOsirion, or AddOsirionTheming together with
builder.Services.AddJSComponents(), which loads its script. See Theming for the setup.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
Id |
string? |
null |
The id of the toggle button. No id is written when it is null, so several toggles can share a page without repeating an id; set it only when a host stylesheet or script needs to address one. |
OnThemeChanged |
EventCallback<string> |
- | A callback raised with light or dark after the visitor switches the theme. It runs only when the toggle itself is interactive (Interactive Server, WebAssembly or Auto): under Static SSR the browser switches the theme and the server is not notified, so the callback is never raised there. Listen for the osirion-theme-update browser event on window to react in every render mode. |
Theme |
ThemeMode |
ThemeMode.System |
The component's color mode. Light or Dark writes it to data-theme (data-bs-theme under Bootstrap) on the root element; System writes nothing. |
Class |
string? |
null |
Extra CSS classes for the component's root element. |
Style |
string? |
null |
An inline style for the component's root element. |
The last three rows, Theme, Class and Style, are inherited from the component base class. They are not written to
the markup, and neither are extra attributes.
Behavior
The button has type="button", and a click listener in _content/Osirion.Blazor.Theming/js/theme-toggle.js handles
the click. It needs JavaScript, but no interactive render mode; there is no fallback without JavaScript. One click:
- switches
data-osirion-themeon<html>betweenlightanddark, and updates the framework's dark-mode marker (data-bs-theme,mud-theme-dark,fluent-dark-themeandfluent-light-theme, orrz-dark-theme); - stores the choice in the
osirion-preferred-themecookie (path/, one year,SameSite=Lax); - raises the
osirion-theme-updateevent onwindow, whichOsirionStylesand your own scripts can listen to; when the toggle is interactive and has anOnThemeChangedcallback, that callback is raised too, withlightordark; - updates every toggle on the page to show the new state.
The toggle has two states, light and dark. Before the visitor clicks, the site follows the configured DefaultMode,
which can be System; after a click, the stored choice wins over the operating system preference.
On the server the button renders from IThemeService.CurrentMode, which reads the same cookie, so a returning visitor
gets the right icon in the first response. When the mode is System, the server renders the moon icon and the script
corrects it once the page loads. The script also refreshes the button after enhanced navigation.
The change fades through a short full-screen overlay. With prefers-reduced-motion: reduce, the theme changes at once
without the fade. The scroll position is kept.
The toggle writes no inline script and no event handler attribute. Its settings (data-default-theme and
data-framework) travel on the button, and a click listener in theme-toggle.js does the work, so the button runs
under a Content Security Policy without 'unsafe-inline'. OsirionStyles, which the page also needs, writes a short
inline script; pass it a Nonce so the policy can allow that script, as its page
describes.
Accessibility
- The button's
aria-labelandtitlestate the current theme and the one a click switches to, for example "Toggle theme. Currently using light mode. Click to switch to dark mode." - A visually hidden text (
osirion-visually-hidden) repeats the current theme; the icons arearia-hidden. - Keyboard focus shows a 2px outline (
:focus-visible); mouse focus does not. - The button is 44 by 44 pixels, 40 by 40 at 768px and below, and 36 by 36 at 576px and below.
Styling
| Class | Element |
|---|---|
theme-switcher |
The wrapper div (10px padding). |
theme-toggle-btn |
The round button. |
theme-icon |
The sun or moon icon. |
theme-text |
The visually hidden text. |
The button takes its light and dark colors from [data-osirion-theme]. The focus outline and the hover ring use
--osirion-primary-color and --osirion-primary-color-hover. The theme does not set these, so the fallbacks
#2563eb and #1d4ed8 apply until you define them:
:root {
--osirion-primary-color: #0b5ed7;
--osirion-primary-color-hover: #0a58ca;
}
The component's own styles are scoped CSS, and several of them use !important. To restyle the button, use a
selector that is at least as specific and add !important where the original has it.
Related
- Theming: setup, options and how the theme is applied.
- OsirionStyles: the component that applies the stored theme.
- Navigation: the
Menucomponent used above.