Actions

AppThemeToggle

The light/dark switch — one disc, or one button per mode, that flips the whole page with a wave, a circle, a wipe, blinds or a fade.

<app-theme-toggle effect="wave" />

A theme toggle is the one control that changes every pixel on the page at once, so the change deserves to be seen: press it and the new theme sweeps across the screen. Dark rises from the bottom, light falls from the top, and the part the edge has passed is already the new theme while the rest is still the old one.

It owns the theme end to end. It reads the stored choice, writes data-theme and data-bs-theme on <html> (which the design system and Bootstrap both read), remembers the choice in localStorage, and runs the switch through the shared engine in @webority/ui-elements. The Razor <app-theme-toggle> and the React <AppThemeToggle> call the same code, so both surfaces store, apply and animate identically.

Playground

Every attribute, live — press the disc to switch this page. Each change re-renders the real tag helper on the server, so the markup underneath is the exact call that produced the disc above it.

<app-theme-toggle storage-key="sc-theme" />
The whole site switchesEvery toggle on this page uses the catalogue's own storage key, so a press here is the real thing: the sidebar disc follows, and the choice survives a reload.

Effects

Six ways to cross the screen. Each one here switches this page, so press them in turn. Dark always comes up from the bottom and light down from the top; a circle grows from the disc you pressed.

wave
circle
slide
blinds
fade
none
When there is no animationThe effect needs the browser's View Transitions API. Without it, when the user prefers reduced motion, with effect="none", or when the page already shows the requested theme (dark to auto on a dark system), the switch is instant. Nothing else changes.

Variants

icon (default) is one disc that cycles through the modes on each press. segmented lays the modes out side by side, like app-segmented, so each mode is a button of its own and a settings page can show the choice.

variant="segmented"
Show code
<app-theme-toggle variant="segmented" include-auto="true" storage-key="sc-theme" />
Own option labels
Show code
<app-theme-toggle variant="segmented" effect="circle" light-label="Day" dark-label="Night" storage-key="sc-theme" />

Modes

Two modes by default, light and dark, so every press visibly changes the page. include-auto adds a third, system-following step: light, dark, auto, light.

include-auto
Show code
<app-theme-toggle include-auto="true" storage-key="sc-theme" />
auto can look like no changeOn a light machine, auto resolves to light, so the step from dark to auto looks like the step to light, and the next press (auto to light) changes nothing visible. Offer auto where users expect it (a settings page); a header disc is usually better with two modes.

Setup

The script applies the stored choice when it runs, which is after the page has painted. To avoid a flash of the wrong theme before that, stamp the choice on <html> in an inline script in <head>; the toggle then takes over from that state.

Pre-paint stamp (_Layout.cshtml)
Change 'theme' to your storage-key. Drop the auto branch if you do not offer it.

Both sandboxes carry this script; the page you are reading loaded straight into its stored theme.

Show code
<script>
  (function () {
    var t = localStorage.getItem('theme');
    t = t === 'dark' || t === 'auto' ? t : 'light';
    var dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    document.documentElement.setAttribute('data-theme', t);
    document.documentElement.setAttribute('data-bs-theme', t === 'auto' ? (dark ? 'dark' : 'light') : t);
  })();
</script>

Use it when

  • One disc in the app chrome (the topbar, the sidebar foot) that switches the whole product
  • A settings row that offers light, dark and system (include-auto)

Reach for something else when

  • A per-section or per-card theme: the switch is page-wide by design
  • Branding colour choices: that is a theme variable, not a mode

Attributes

AttributeTypeDefaultDescription
varianticon | segmentediconOne disc that cycles through the modes, or one option per mode laid out like app-segmented.
effectwave | circle | slide | blinds | fade | nonewaveThe page-wide effect. An unknown value throws at render.
include-autobooleanfalseOffer a third, system-following mode: light, dark, auto.
storage-keystring"theme"The localStorage key the choice is kept under. Match it in the pre-paint stamp.
durationint—Effect length in ms. Each effect has its own default (wave 1100, circle 700, slide 600, blinds 900, fade 350).
labelstring"Theme"Accessible name and tooltip, shown with the current mode: "Theme: dark".
light-labelstring"Light"The segmented variant's option text for light.
dark-labelstring"Dark"The segmented variant's option text for dark.
auto-labelstring"System"The segmented variant's option text for auto.
namestring—The segmented variant's field name (what groups its radios). Defaults to a generated one.
sizesm | md | lg | xlmdThe disc's glyph size (16 / 20 / 24 / 28px; the disc scales with it) or the segmented strip's size, like app-segmented.
tooltip-placementauto | top | bottom | start | endautoPreferred tooltip side; auto picks the side with the most room.
classstring—Extra classes on the disc.