Theme provider
Light, dark, or follow the operating system - remembered between visits and applied before the first paint.
The toggle
Two states, or three if you want to hand control back to the operating system. Both are the same component.
<VetoThemeToggle />
<VetoThemeToggle ShowSystemOption="true" />Reading the theme from code
Inject IVetoThemeService anywhere to read or change the theme. No component required, and every toggle on the page stays in step because they all share the service.
@inject IVetoThemeService ThemeService
<span>Mode: @ThemeService.Mode</span>
<span>Resolved: @(ThemeService.IsDark ? "Dark" : "Light")</span>
@code {
// Change it from anywhere
private Task GoDark() => ThemeService.SetModeAsync(VetoThemeMode.Dark);
}Rebranding
Override the tokens you care about and leave the rest. This site's red palette is exactly this - a VetoTheme, with no component CSS anywhere.
public static readonly VetoTheme Theme = new()
{
Light = new VetoPalette
{
Primary = "#C2121F",
Surface = "#FFFFFF",
Text = "#1A1517",
Border = "#E7DDDE"
},
Dark = new VetoPalette
{
Primary = "#FF3B47",
Surface = "#0B0A0B",
Text = "#F4EFEF",
Border = "#2E2729"
},
RadiusMedium = "6px"
};<VetoThemeProvider Theme="AppTheme" />Avoiding the flash
A prerendered page is HTML long before WebAssembly starts. Without the boot script the page renders light, then snaps to dark once the app takes over - on every navigation. The script is tiny and synchronous on purpose: deferred, async, or module scripts all run after the first paint, which is exactly too late.
<head>
<script src="_content/Veto.Theming/veto-theme-init.js"></script>
<link rel="stylesheet" href="_content/Veto.Core/VetoStyles.css" />
</head>