@adrienlcp/theme-preference
Light, dark or system theme with no flash on load, and a browser toolbar that follows the choice
Works with
- TypeScript
- CSS
- Reactoptional peer
- Viteoptional peer
Install
pnpm add @adrienlcp/theme-preferenceExports
Grouped by the section of the documentation that explains them. A row leads to its section.
The markup1 export
The store1 export
Before the first paint2 exports
prePaintScriptForThe inline script that applies a stored explicit scheme before the first paint, so the page never flashes the system palette first.@adrienlcp/Functiontheme-preference themePreferencePluginAppends the pre-paint script to the<head>of every page Vite serves or builds, after thetheme-colortags.@adrienlcp/Functiontheme-preference/ vite
React1 export
Documented in the source only10 exports
THEME_COLOR_SELECTOREach tag names the scheme it colours:<meta name="theme-color" data-scheme="dark">.@adrienlcp/Constanttheme-preference themeColorMediaForThemediaatheme-colortag of the given scheme must carry.@adrienlcp/Functiontheme-preference applyThemePreferenceStampsdata-themeon<html>for an explicit scheme, removes it for'system'so the stylesheet'sprefers-color-schemerules answer, and points everytheme-colortag at the same palette.@adrienlcp/Functiontheme-preference COLOR_SCHEMES@adrienlcp/Constanttheme-preference ColorSchemeA palette the page can be painted in.@adrienlcp/Typetheme-preference THEME_PREFERENCES@adrienlcp/Constanttheme-preference ThemePreferenceWhat the visitor chose.@adrienlcp/Typetheme-preference isColorScheme@adrienlcp/Functiontheme-preference isThemePreference@adrienlcp/Functiontheme-preference ThemePreferenceStore@adrienlcp/Typetheme-preference
Documentation
The README and the documents beside it, as written in the repository.
OverviewREADME.md
Light, dark or system theme: no flash of the wrong palette on load, and a
browser toolbar that follows the choice. No third-party dependency: storage goes
through @adrienlcp/safe-storage.
The detail most theme switchers miss: a phone paints its address bar from
<meta name="theme-color">. Those tags ship scoped to
prefers-color-scheme, so a visitor who picks dark on a light system keeps a
light toolbar over a dark page. This package switches each tag's media
between all, not all and the media query, both before the first paint and
live.
pnpm add @adrienlcp/theme-preferenceThe markupREADME.md
One theme-color tag per scheme, each saying which scheme it colours:
<meta name="theme-color" data-scheme="light" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" data-scheme="dark" content="#101010" media="(prefers-color-scheme: dark)" />The stylesheet follows the system by default and yields to data-theme. The
package ships it: import @adrienlcp/theme-preference/color-scheme.css once
(or @use it from Sass), and write every colour token as
light-dark(<light>, <dark>):
:root { color-scheme: light dark; }
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }The file is unlayered, so it wins over any color-scheme a layered stylesheet
sets.
The storeREADME.md
// theme.ts
import { createThemePreferenceStore } from '@adrienlcp/theme-preference'
export const themeStore = createThemePreferenceStore({ storageKey: 'app:theme' })getPreference(), setPreference(preference) and subscribe(listener).
setPreference persists the choice, stamps the document and notifies every
subscriber. A preference is 'system' | 'light' | 'dark'; 'system' is stored
as no entry at all. Storage that throws, as in a Safari private window, reads as
'system' and keeps a new choice for the current page view.
Before the first paintREADME.md
The pre-paint script must run in <head>, after the theme-color tags. With
Vite, the plugin appends it to every page, reading the key from the store:
// vite.config.ts
import { themePreferencePlugin } from '@adrienlcp/theme-preference/vite'
import { themeStore } from './src/theme'
export default defineConfig({ plugins: [themePreferencePlugin(themeStore)] })Without Vite, inline themeStore.prePaintScript (or
prePaintScriptFor(key)) yourself.
ReactREADME.md
import { useThemePreference } from '@adrienlcp/theme-preference/react'
const ThemeSwitch = () => {
const { preference, setPreference } = useThemePreference(themeStore)
// …
}Every component using the hook re-renders on a change. A server render reads
'system'.