useTheme
Returns the application's light/dark theme (choice, effective theme and theme list) and changes it in html, the cookie and other tabs.
Se autoimporta en los componentes y en app/.
import { useTheme } from "@meekojs/meeko/framework";With theme in meeko.config.ts, Meeko stores the user's choice in a cookie, writes the theme to <html> from the first byte and, when the server cannot know it, applies it with a <head> script before the first render. useTheme() gives components that state: theme, the choice (including "system"), resolved, the visible theme, themes, the theme list, and set, which changes the choice in <html>, color-scheme, the cookie and localStorage.
For a ready-made selector, use <UIThemeToggle> from @meekojs/ui 0.2; for a custom one, use useThemeToggle(). <MeekoThemeToggle> is deprecated since core 1.8 and removed in 2.0. useTheme() is auto-imported in .meeko files (your own export with that name takes priority) and is called while a component script runs.
Usage#
// meeko.config.ts
import { defineMeekoConfig } from '@meekojs/meeko/framework'
export default defineMeekoConfig({ theme: true }) // light and dark, "system" by default<!-- app/components/ThemeSelect.meeko -->
<script lang="ts">
const { theme } = useTheme()
</script>
<template>
<select
aria-label="Theme"
model="theme"
>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</template>theme is a writable signal: model changes it, equivalent to calling set.
Type#
function useTheme(): ThemeStateinterface ThemeState<
Name extends string = ThemeName,
Choice extends string = ThemeChoice,
> {
readonly theme: Signal<Choice>;
readonly resolved: ReadonlySignal<Name>;
readonly themes: readonly Name[];
set(theme: Choice): void;
}Parameters#
useTheme() takes no parameters. Themes come from theme in meeko.config.ts (themes, default, system, attribute, cookie…).
Return value#
A ThemeState, shared by the entire application:
| Property | Type | Description |
|---|---|---|
theme |
Signal<Choice> |
The choice, including "system". Writable in the browser (assignment is equivalent to set); read-only on the server. |
resolved |
ReadonlySignal<Name> |
The effective theme: the choice or, in "system", that of prefers-color-scheme, tracking system changes. |
themes |
readonly Name[] |
Configured themes, without "system". |
set(theme) |
void |
Changes the choice. A name that is neither a theme nor "system" throws TypeError: useTheme(): unknown theme "…"; on the server throws Error: useTheme(): the theme changes in the browser, not during a server render. |
meeko check types Name and Choice from configuration: with themes: ["light", "dark", "sepia"], resolved is "light" | "dark" | "sepia". Without theme in configuration, useTheme() throws Error: useTheme() needs theme in meeko.config.ts (theme: true) and a running Meeko application.
Examples#
A light/dark toggle button without an icon jump#
Render both icons and let CSS display one according to the <html> attribute, written by the <head> script before the first render:
<!-- app/components/ThemeButton.meeko -->
<script lang="ts">
const { resolved, set } = useTheme()
const toggle = (): void => set(resolved.value === 'dark' ? 'light' : 'dark')
</script>
<template>
<button
type="button"
class="theme-button"
aria-label="Theme oscuro"
bind:aria-pressed="String(resolved.value === 'dark')"
on:click="toggle"
>
<svg
class="sun"
viewBox="0 0 24 24"
aria-hidden="true"
>
<circle
cx="12"
cy="12"
r="5"
/>
</svg>
<svg
class="moon"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5Z" />
</svg>
</button>
</template>
<style>
.theme-button .moon {
display: none;
}
:root[data-theme='dark'] .theme-button .sun {
display: none;
}
:root[data-theme='dark'] .theme-button .moon {
display: inline;
}
</style>Custom themes#
// meeko.config.ts
import { defineMeekoConfig } from '@meekojs/meeko/framework'
export default defineMeekoConfig({
theme: { themes: ['light', 'dark', 'sepia'], default: 'light', darkThemes: ['dark'] },
})<script lang="ts">
const { theme, themes } = useTheme()
</script>
<template>
<fieldset>
<legend>Theme</legend>
<label
each:of="themes"
each:as="name"
each:key="name"
>
<input
type="radio"
bind:value="name"
model="theme"
/>
{name}
</label>
</fieldset>
</template>Read the theme for a library#
A chart needing different dark-mode colors tracks resolved with an effect:
<script lang="ts">
const { resolved } = useTheme()
const chartRef = useElementRef('chart')
onMounted(() => {
const chart = createChart(chartRef.value!)
effect(() => chart.setPalette(resolved.value === 'dark' ? darkPalette : lightPalette))
return () => chart.destroy()
})
</script>
<template><canvas ref:chart></canvas></template>Common mistakes#
Use it without theme in configuration#
// meeko.config.ts without theme
const { resolved } = useTheme()Rendering throws Error: useTheme() needs theme in meeko.config.ts (theme: true) and a running Meeko application. Enable the theme:
export default defineMeekoConfig({ theme: true })A theme that does not exist#
const { set } = useTheme()
const go = () => set('purple')With themes: ["light", "dark", "sepia"], meeko check reports TS2345: Argument of type '"purple"' is not assignable to parameter of type '"dark" | "light" | "sepia" | "system"'; in JavaScript, set throws TypeError: useTheme(): unknown theme "purple"; use "system", "light", "dark", "sepia".
const go = () => set('sepia')Choose the icon with resolved on a prerendered page#
<span>{resolved.value === "dark" ? "🌙" : "☀️"}</span>This works when the server knows the theme (the cookie or client hint), but on a prerendered page, with the static adapter or in "system" without a client hint, HTML renders the light theme and the icon changes when hydration ends. Render both and let CSS decide using the <html> attribute:
<span
class="icon-sun"
aria-hidden="true"
>
☀️
</span>
<span
class="icon-moon"
aria-hidden="true"
>
🌙
</span>
<!-- :root[data-theme="dark"] .icon-sun { display: none } … -->Notes#
- SSR. The server reads the request cookie and, in
"system", theSec-CH-Prefers-Color-Schemeclient hint. If it does not know the preference,resolvedissystem's light theme. - Hydration. The browser hydrates with the server-rendered values (transferred in page state) and receives its own when hydration ends, before
onMounted: there is never a mismatch. - Other tabs. A change in another tab arrives through
localStorage'sstorageevent; in"system",resolvedtracksprefers-color-schemewhile the page is open. - Navigation. Client navigation preserves the theme because it does not reload
<html>. - Security. The cookie is not
HttpOnly(the client writes it) and contains nothing private. The server only accepts a theme fromthemesor"system"; any other value is ignored. - CSS. Declare
color-schemein each theme's CSS so the page has the correct scheme even without JavaScript; Meeko also applies it through CSSOM, allowed by a strict CSP.