Actions

ThemeToggle

Light, dark or system, set on <html> for the whole page and remembered in the browser.

#button#theme#settings#interactive

Demo

Preview
Theme: system. It changes this whole page.

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/theme_toggle.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/theme_toggle.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/theme_toggle.qwe
component ThemeToggleDemoLive() {    state t = "system"    <div.demo-row>        <ThemeToggle on_change={(v) => { t = v }}/>        <span.demo-note>Theme: {t}. It changes this whole page.</span>    </div>}view ThemeToggleDemo() =    <div.demo-stack>        <ThemeToggleDemoLive/>        <div.demo-row>            <ThemeToggle variant="button"/>            <ThemeToggle size="sm" light="Day" dark="Night" system="Auto"/>        </div>    </div>

Source

ui/theme_toggle.qwe
// ThemeToggle: light, dark or system, as a row of three buttons or one that cycles.// It sets `ui-light` or `ui-dark` on <html> (nothing for system), which the theme// follows everywhere, and remembers the choice in the browser, so the next page// starts with it (put a ThemeToggle on each page, or any component that calls// theme_toggle_apply on mount).////   <ThemeToggle/>//   <ThemeToggle variant="button" size="sm"/>//   <ThemeToggle value="dark" on_change={(t) => { save_theme(t) }}/>//// value: the theme to start with ("system") when none is remembered. variant:// "segmented" (default: three buttons) or "button" (one, cycling light → dark →// system). remember: the localStorage key ("ui-theme-toggle"; null to not remember).// labels: light ("Light"), dark ("Dark"), system ("System"). label: the group's// name for screen readers ("Theme"). size: "sm" or "md" (default).// on_change(theme): inside one of your components, given "light", "dark" or "system".import ui/theme.qwe use ui_clslet theme_toggle_css = css`.ui-theme-toggle { display: inline-flex; align-items: center; gap: 2px; padding: 3px; border: 1px solid var(--ui-border); border-radius: 999px; background: var(--ui-subtle); vertical-align: middle; }.ui-theme-toggle-btn {  display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: 30px; min-width: 30px; padding: 0 12px; border: 0;  border-radius: 999px; background: transparent; color: var(--ui-muted); font: 500 13px/1 var(--ui-font); cursor: pointer;  transition: background .15s, color .15s;}.ui-theme-toggle-btn:hover { color: var(--ui-fg); }.ui-theme-toggle-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-theme-toggle-btn[aria-pressed="true"] { background: var(--ui-surface); color: var(--ui-fg); box-shadow: var(--ui-shadow); }.ui-theme-toggle-btn svg { flex: none; width: 16px; height: 16px; }.ui-theme-toggle-one { padding: 0; border: 0; background: transparent; }.ui-theme-toggle-one .ui-theme-toggle-btn { border: 1px solid var(--ui-border); background: var(--ui-surface); color: var(--ui-fg); }.ui-theme-toggle-one .ui-theme-toggle-btn:hover { background: var(--ui-subtle); }.ui-theme-toggle-sm .ui-theme-toggle-btn { height: 26px; min-width: 26px; padding: 0 9px; font-size: 12px; }.ui-theme-toggle-sm .ui-theme-toggle-btn svg { width: 14px; height: 14px; }@media (prefers-reduced-motion: reduce) { .ui-theme-toggle-btn { transition: none; } }`// Sets the theme on <html>: "light", "dark", or "system" (neither class).fn theme_toggle_apply(theme: str) {    let root = js("document.documentElement")    if root != null {        root.classList.remove("ui-light")        root.classList.remove("ui-dark")        if theme == "light" || theme == "dark" { root.classList.add(`ui-${theme}`) }    }}fn theme_toggle_icon(theme: str) =    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">        {match theme {            "light" => <path d="M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10ZM12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"/>            "dark" => <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"/>            _ => <path d="M2 5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2ZM8 21h8M12 17v4"/>        }}    </svg>component ThemeToggle(value: str = "system", variant: str = "segmented", remember: str? = "ui-theme-toggle", light: str = "Light",                      dark: str = "Dark", system: str = "System", label: str = "Theme", size: str = "md", on_change = null,                      class: str? = null) {    style global theme_toggle_css    state theme = value    let names = { light, dark, system }    let pick = (t: str) => {        theme = t        theme_toggle_apply(t)        if remember != null {            if t == "system" { js("localStorage.removeItem", remember) } else { js("localStorage.setItem", remember, t) }        }        on_change && on_change(t)    }    let order = ["light", "dark", "system"]    let next = (t: str) => match t { "light" => "dark", "dark" => "system", _ => "light" }    <div class={ui_cls(["ui-theme-toggle", variant == "button" && "ui-theme-toggle-one", size == "sm" && "ui-theme-toggle-sm", class])}         role="group" aria-label={label}         @mount={(_el) => {             let saved = remember != null ? js("localStorage.getItem", remember) : null             if saved == "light" || saved == "dark" { theme = saved }             theme_toggle_apply(theme)         }}>        {variant == "button"            ? <button.ui-theme-toggle-btn type="button" aria-label={`${label}: ${get(names, theme)}`} title={`${label}: ${get(names, theme)}`}                      @click={() => { pick(next(theme)) }}>                {theme_toggle_icon(theme)}                <span>{get(names, theme)}</span>              </button>            : for t in order => <button.ui-theme-toggle-btn key={t} type="button" aria-pressed={theme == t ? "true" : "false"}                      title={get(names, t)} @click={() => { pick(t) }}>                {theme_toggle_icon(t)}                <span>{get(names, t)}</span>              </button>}    </div>}
Thread
65Useful? Like it, or add to its thread below.

Thread 0 posts

On ThemeToggle: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.