ThemeToggle
Light, dark or system, set on <html> for the whole page and remembered in the browser.
Demo
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.qwemain.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>}Related
BookmarkButtonA save-for-later toggle whose bookmark fills when pressed, with or without its label.8 views · 8 downloadsButtonA button, or a link that looks like one: four variants, three tones, three sizes, loading.7 views · 7 downloadsButtonGroupButtons joined edge to edge into one control, or spaced in a row; across or upright.6 views · 6 downloadsCopyButtonCopies text to the clipboard and says so for a moment, to the eye and to screen readers.4 views · 6 downloadsDownloadButtonA link that downloads a file, showing its kind and size so nobody is surprised by a big one.7 views · 6 downloadsDropdownMenuA button that opens a short list of links and actions; Escape or a click elsewhere closes it.6 views · 7 downloads
Thread
Thread 0 posts
On ThemeToggle: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.