{}

Theming

BESPA bakes Material Design 3 into the framework: every built-in widget references the design tokens (--md-sys-color-*, --md-sys-typescale-*) so a page automatically picks up your theme. This page is about the app-level controls — picking dark vs. light, choosing a key color palette, and persisting the user’s preference.

Light, dark, or system

Three methods on Page set the appearance mode:

// Always render dark.
page := wf.Page().WithThemeDark()

// Always render light.
page := wf.Page().WithThemeLight()

// Follow the OS / browser preference (the default).
page := wf.Page().WithThemeDefault()

The default follows the browser’s prefers-color-scheme media query, so the page tracks the OS setting and even switches live if the user toggles the system theme. WithThemeDark / WithThemeLight force one or the other regardless of the OS.

Key color palettes

Material 3 generates an entire token palette — primary / secondary / tertiary / error / surface, with their on-color counterparts — from a single “source” color. The css package exposes presets plus a constructor that derives a palette from any color string:

// Pick from the framework's preset palettes.
page := wf.Page().WithKeyColors(css.PresetKeyColors[0])

// Or build your own from a single source color.
custom := css.KeyColorsFromString("violet")
page := wf.Page().WithKeyColors(custom)

All built-in widgets and any widget that references --md-sys-color-* recolors automatically; no widget needs to know which palette is in use.

Persisting the preference

User-specific preferences belong in the session, not the URL. The website’s shared.Render wrapper reads the active session’s saved theme/palette and applies them to every page before drawing:

func Render(w http.ResponseWriter, r *http.Request, page *widget.PageWidget) {
    session := SessionOf(w, r)
    switch session.Theme {
    case "Dark":
        page.WithThemeDark()
    case "Light":
        page.WithThemeLight()
    }
    if session.Palette != "" {
        for _, kc := range css.PresetKeyColors {
            if kc.Name == session.Palette {
                page.WithKeyColors(kc)
                break
            }
        }
    }
    page.Draw(w, r)
}

For a real app, swap the in-memory session for whatever persistence you use; the page-side API stays the same.

How the tokens get to the browser

On the first request the framework writes the resolved palette and typography scale to /bespa/tones.css and /bespa/style.css. Browsers cache those CSS files; a 24-hour cache header keeps revisits cheap. When the user switches themes, the new key colors are picked up on the next page navigation — there’s no client-side palette computation.

See also

Extend → Material theming — how custom widgets should reference tokens so they recolor for free.

Profile page — a working theme/palette switcher backed by session storage.