Dark mode
Not a mode — a palette. Build both themes at startup, swap a pointer.
lotusui has no dark mode: dark is a Palette like any other,
applied with WithPalette. DefaultDarkPalette ships in the box — the
same lavender brand on a deep cool-gray canvas, every ladder keeping the light palette's ORDER
(faint → prominent), so components never know which world they're in. Try it live: pick
Midnight in this site's palette picker — every demo on every page flips.
Usage
Construct both themes once at startup; following the system appearance (or a user setting) is a pointer swap — no rebuild, no reload, no per-frame cost.
light := lotusui.NewTheme()
dark := lotusui.NewTheme(lotusui.WithPalette(lotusui.DefaultDarkPalette))
th := light
if prefersDark {
th = dark
}
Custom dark palettes
A dark palette follows two rules. Panels sit slightly LIGHTER than the canvas —
elevation still reads as "closer to the light", exactly as white cards do on the tinted light
page. And status pairs invert their construction: light ink on a deep tinted well, instead of
deep ink on a pastel. Start from DefaultDarkPalette and override tokens the same
way you would the light one.
night := lotusui.DefaultDarkPalette
night.BrandSolid = myBrand // pastels pop on dark even better
night.BrandFg = myBrandLight // the readable ink flips to a light sibling
dark := lotusui.NewTheme(lotusui.WithPalette(night))
Contrast stays checked
The build-time theme generator validates dark palettes with the same WCAG
contrast checks as light ones — lotusui theme -config theme.json -strict makes an
unreadable pairing a build failure, whichever world it is in.