@codenhub/theme 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +17 -429
- package/dist/index.d.ts +41 -15
- package/dist/index.js +1 -1
- package/docs/changelog/0.1.2.md +15 -0
- package/docs/changelog/index.md +9 -0
- package/docs/index.md +60 -0
- package/docs/reference/index.md +381 -0
- package/docs/ssr-and-pre-paint.md +46 -0
- package/docs/tokens-and-persistence.md +51 -0
- package/llms-full.txt +213 -0
- package/llms.txt +19 -0
- package/package.json +29 -17
package/docs/index.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Overview
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Manage Browser Themes
|
|
6
|
+
|
|
7
|
+
`@codenhub/theme` owns one thing for a browser application: deciding which theme is active, keeping it in sync with `localStorage` and the OS `prefers-color-scheme` setting, and reflecting it onto the DOM as an attribute, a class, and CSS custom properties. It fits applications that want one place to own that decision instead of scattering `matchMedia` listeners and `localStorage` reads across the codebase, while keeping selectors, visual design, and CSS entirely in application code.
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
### Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pnpm add @codenhub/theme
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Quick start
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createTheme } from "@codenhub/theme";
|
|
21
|
+
|
|
22
|
+
const theme = createTheme().init();
|
|
23
|
+
theme.set("dark");
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`createTheme()` with no arguments configures the built-in `light` and `dark` themes. `init()` resolves the theme to apply — a stored preference if one is valid, otherwise the OS preference — and applies it to `document.documentElement`. Call `destroy()` when the manager's owner is torn down, such as in a framework component's cleanup hook; it stops listening but leaves the DOM and stored preference as they were unless told otherwise (see [Tokens, persistence, and cross-tab sync](tokens-and-persistence.md)).
|
|
27
|
+
|
|
28
|
+
### Configuration
|
|
29
|
+
|
|
30
|
+
Most applications configure at least `themes` and `tokenSchema` up front:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createTheme } from "@codenhub/theme";
|
|
34
|
+
|
|
35
|
+
const theme = createTheme({
|
|
36
|
+
themes: [
|
|
37
|
+
{ name: "light", colorScheme: "light", tokens: { primary: "#171717" } },
|
|
38
|
+
{ name: "dark", colorScheme: "dark", tokens: { primary: "#f9fafb" } },
|
|
39
|
+
],
|
|
40
|
+
tokenSchema: { primary: "--color-primary" },
|
|
41
|
+
storageKey: "app-theme-preference",
|
|
42
|
+
isTailwindCss: true,
|
|
43
|
+
}).init();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`themes` replaces the built-in list entirely — include `light` and `dark` (or whatever names `defaultTheme` and `systemTheme` point at) when defining custom themes. `tokenSchema` maps the typed token names used in `ThemeDefinition.tokens` and `get()` to the CSS custom property each one writes to `document.documentElement`. See [Tokens, persistence, and cross-tab sync](tokens-and-persistence.md) for how token values are resolved and stored, and [SSR, pre-paint, and Tailwind](ssr-and-pre-paint.md) for flash-of-unstyled-content prevention and OS-preference-only applications like `isTailwindCss`.
|
|
47
|
+
|
|
48
|
+
Construction validates every option and throws on invalid configuration: empty or duplicate theme names, a `defaultTheme` or `systemTheme` name that is not in `themes`, an invalid `colorScheme`, or a `shouldApplyClass` resolver that would produce an empty or whitespace-containing class.
|
|
49
|
+
|
|
50
|
+
## Requirements
|
|
51
|
+
|
|
52
|
+
- Browser integration uses `document.documentElement`, `localStorage`, `matchMedia`, `storage` events, and `CustomEvent`. Every one of these is read defensively: an unavailable or throwing API is treated as absent rather than crashing the caller.
|
|
53
|
+
- SSR is supported by skipping unavailable browser work and using the configured `defaultTheme`. The package cannot produce server HTML attributes or prevent a flash on its own — see [SSR, pre-paint, and Tailwind](ssr-and-pre-paint.md).
|
|
54
|
+
- Consumers provide CSS selectors, variables, and visual tokens. The package only ever writes an attribute, optional classes, and CSS custom properties — it ships no CSS or token values beyond the built-in `light`/`dark` definitions' names.
|
|
55
|
+
|
|
56
|
+
## Next steps
|
|
57
|
+
|
|
58
|
+
- [Tokens, persistence, and cross-tab sync](tokens-and-persistence.md): How `get()` resolves token values, how preferences persist to `localStorage` and sync across tabs, and how to read or clear the stored preference.
|
|
59
|
+
- [SSR, pre-paint, and Tailwind](ssr-and-pre-paint.md): Preventing a flash of unstyled content before hydration, server-rendering considerations, and toggling Tailwind's `dark` class.
|
|
60
|
+
- [API reference](reference/index.md): Every exported function, type, and interface member with its full signature.
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: /
|
|
3
|
+
group: Reference
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- Generated by `pnpm generate` from packages/theme/src. Do not edit. -->
|
|
7
|
+
|
|
8
|
+
# @codenhub/theme
|
|
9
|
+
|
|
10
|
+
## Functions
|
|
11
|
+
|
|
12
|
+
### createTheme
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
export declare function createTheme<TSchema extends Record<string, string> = Record<string, string>>(options?: ThemeOptions<TSchema>): Theme<TSchema>;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Factory function that creates and returns a `Theme` instance.
|
|
19
|
+
|
|
20
|
+
**Parameters**
|
|
21
|
+
|
|
22
|
+
- `options` — Configuration options for theme definitions, persistence keys, DOM attributes, custom class resolvers, and dynamic token schemas.
|
|
23
|
+
|
|
24
|
+
**Returns** — A `Theme` instance.
|
|
25
|
+
|
|
26
|
+
**Throws** — If configured theme names are empty, duplicated, invalid for CSS class application, have an invalid `colorScheme`, or if the default/system themes are not present in the configured list.
|
|
27
|
+
|
|
28
|
+
### getPrePaintScript
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
export declare function getPrePaintScript<TSchema extends Record<string, string> = Record<string, string>>(options?: ThemeOptions<TSchema>): string;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Generates a synchronous inline IIFE script string to inject into document `<head>`. Prevents Flash of Unstyled Content (FOUC) by applying storage or system theme before render.
|
|
35
|
+
|
|
36
|
+
**Parameters**
|
|
37
|
+
|
|
38
|
+
- `options` — Configuration options used to determine storage keys, attributes, default/system themes, custom class resolvers, and token schemas.
|
|
39
|
+
|
|
40
|
+
**Returns** — Minified JavaScript script string.
|
|
41
|
+
|
|
42
|
+
## Interfaces
|
|
43
|
+
|
|
44
|
+
### SystemThemeMap
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
export interface SystemThemeMap
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Mapping from OS color-scheme preferences to configured theme names.
|
|
51
|
+
|
|
52
|
+
#### dark
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
dark: string;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Configured theme name used when the OS preference is dark.
|
|
59
|
+
|
|
60
|
+
#### light
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
light: string;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Configured theme name used when the OS preference is light or no dark preference is detected.
|
|
67
|
+
|
|
68
|
+
### Theme
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
export interface Theme<TSchema extends Record<string, string> = Record<string, string>>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Core theme preference handler. Manages initialization, switching themes, persistence to localStorage, synchronizing with the OS prefers-color-scheme preference, dynamic token mapping to CSS Custom Properties, and dispatching change events.
|
|
75
|
+
|
|
76
|
+
#### clearPreference
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
clearPreference(): ThemeDefinition<TSchema>;
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Removes the explicit user theme preference from storage and resets the theme to match the OS system preference.
|
|
83
|
+
|
|
84
|
+
#### destroy
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
destroy(options?: { revertDom?: boolean; }): void;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Cleans up the theme instance by removing all in-process change listeners, the system preference media query listener, and the cross-tab storage listener. Resets active tokens and the active theme name to the configured `defaultTheme` so the instance can be safely re-initialized with `init()`.
|
|
91
|
+
|
|
92
|
+
**Parameters**
|
|
93
|
+
|
|
94
|
+
- `options` — Optional cleanup options. Set `revertDom: true` to remove configured DOM attributes, classes, and CSS custom properties applied to `document.documentElement`.
|
|
95
|
+
|
|
96
|
+
#### get
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
get(options?: { skipComputed?: boolean; }): ThemeDefinition<TSchema>;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Retrieves the active theme configuration including static and computed tokens.
|
|
103
|
+
|
|
104
|
+
Token values are merged in this priority order (last wins):
|
|
105
|
+
|
|
106
|
+
1. CSS computed style — values read from `window.getComputedStyle` for tokens not defined in JS (unless `options.skipComputed` is true).
|
|
107
|
+
2. Theme static tokens — values defined in `ThemeDefinition.tokens` for the active theme.
|
|
108
|
+
3. Runtime overrides — values passed to `init()`, `set()`, `toggle()`, or other methods.
|
|
109
|
+
|
|
110
|
+
**Parameters**
|
|
111
|
+
|
|
112
|
+
- `options` — Optional getter options. Set `skipComputed: true` to avoid reading computed styles from the DOM via `window.getComputedStyle`, preventing potential layout reflows.
|
|
113
|
+
|
|
114
|
+
#### getPrePaintScript
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
getPrePaintScript(): string;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Generates a synchronous inline IIFE script string to inject into document `<head>`. Prevents Flash of Unstyled Content (FOUC) by applying storage or system theme before render.
|
|
121
|
+
|
|
122
|
+
#### getStored
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
getStored(): string | null;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Retrieves the currently stored theme preference name from `localStorage`.
|
|
129
|
+
|
|
130
|
+
#### getSystem
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
getSystem(): ThemeDefinition<TSchema>;
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Resolves the configured theme that matches the active OS color-scheme preference.
|
|
137
|
+
|
|
138
|
+
#### init
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
init(tokens?: Partial<Record<keyof TSchema, string>>): this;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Initializes the theme manager. Resolves the active theme (using the stored preference if valid, falling back to the current OS color-scheme preference), applies classes/attributes to the DOM, and registers the media query listener for automatic system preference updates.
|
|
145
|
+
|
|
146
|
+
**Parameters**
|
|
147
|
+
|
|
148
|
+
- `tokens` — Optional runtime override token values to merge and apply.
|
|
149
|
+
|
|
150
|
+
#### set
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
set(name: string, tokens?: Partial<Record<keyof TSchema, string>>): ThemeDefinition<TSchema>;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Activates a configured theme by name and updates the stored preference in `localStorage`.
|
|
157
|
+
|
|
158
|
+
**Parameters**
|
|
159
|
+
|
|
160
|
+
- `name` — The name of the configured theme to activate.
|
|
161
|
+
- `tokens` — Optional runtime override token values to apply. Active overrides persist across subsequent theme changes unless cleared (by passing new overrides or an empty object).
|
|
162
|
+
|
|
163
|
+
#### subscribe
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
subscribe(listener: ThemeChangeListener<TSchema>): () => void;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Registers a callback listener to receive notifications when the theme or its tokens change.
|
|
170
|
+
|
|
171
|
+
**Parameters**
|
|
172
|
+
|
|
173
|
+
- `listener` — Callback function invoked on theme changes.
|
|
174
|
+
|
|
175
|
+
#### toggle
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
toggle(tokens?: Partial<Record<keyof TSchema, string>>): ThemeDefinition<TSchema>;
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Switches away from the active theme and persists the change. If the active theme configures a `pairedTheme`, that theme is activated. Otherwise the next theme is selected from `systemTheme.light` or `systemTheme.dark` based on the active theme's `colorScheme`, not by cycling the active theme name.
|
|
182
|
+
|
|
183
|
+
**Parameters**
|
|
184
|
+
|
|
185
|
+
- `tokens` — Optional runtime override token values to apply. Active overrides persist across subsequent theme changes unless cleared (by passing new overrides or an empty object).
|
|
186
|
+
|
|
187
|
+
### ThemeChangeDetail
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
export interface ThemeChangeDetail<TSchema extends Record<string, string> = Record<string, string>>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Payload passed to subscribers and the browser `themechange` event after a theme change.
|
|
194
|
+
|
|
195
|
+
#### name
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
name: string;
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Active theme name after the change.
|
|
202
|
+
|
|
203
|
+
#### source
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
source: ThemeChangeSource;
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Operation or browser signal that caused the change notification.
|
|
210
|
+
|
|
211
|
+
#### theme
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
theme: ThemeDefinition<TSchema>;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Active theme definition after the change.
|
|
218
|
+
|
|
219
|
+
### ThemeDefinition
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
export interface ThemeDefinition<TSchema extends Record<string, string> = Record<string, string>>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Theme option stored, applied to the configured DOM attribute, and mapped to the browser color scheme.
|
|
226
|
+
|
|
227
|
+
#### colorScheme
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
colorScheme: "light" | "dark";
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Browser color scheme applied to `document.documentElement.style.colorScheme`.
|
|
234
|
+
|
|
235
|
+
#### name
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
name: string;
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Unique configured theme name used for storage, DOM attributes, and generated default classes.
|
|
242
|
+
|
|
243
|
+
#### pairedTheme
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
pairedTheme?: string;
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Optional theme name activated when calling `toggle()` from this theme.
|
|
250
|
+
|
|
251
|
+
#### tokens
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
tokens?: Partial<Record<keyof TSchema, string>>;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Optional theme-specific static token values.
|
|
258
|
+
|
|
259
|
+
### ThemeOptions
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
export interface ThemeOptions<TSchema extends Record<string, string> = Record<string, string>>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Configuration for theme definitions, persistence, DOM application, and system preference mapping.
|
|
266
|
+
|
|
267
|
+
#### attribute
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
attribute?: string;
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Attribute set on `document.documentElement` with the active theme name.
|
|
274
|
+
|
|
275
|
+
#### defaultTheme
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
defaultTheme?: string;
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Configured theme name used before initialization and when browser APIs are unavailable.
|
|
282
|
+
|
|
283
|
+
#### isTailwindCss
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
isTailwindCss?: boolean;
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Whether to toggle Tailwind CSS's `dark` class for themes with `colorScheme: "dark"`.
|
|
290
|
+
|
|
291
|
+
#### shouldApplyClass
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
shouldApplyClass?: boolean | ThemeClassResolver<TSchema>;
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Whether and how to apply a theme-specific class to `document.documentElement`.
|
|
298
|
+
|
|
299
|
+
#### storageKey
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
storageKey?: string;
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`localStorage` key used for explicit user preferences.
|
|
306
|
+
|
|
307
|
+
#### systemTheme
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
systemTheme?: SystemThemeMap;
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Configured theme names selected for OS light and dark color-scheme preferences.
|
|
314
|
+
|
|
315
|
+
#### themes
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
themes?: readonly ThemeDefinition<TSchema>[];
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Available themes. Names must be unique, non-empty, and valid default class tokens when `shouldApplyClass` is `true`.
|
|
322
|
+
|
|
323
|
+
#### tokenSchema
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
tokenSchema?: TSchema;
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Schema mapping theme token names to their corresponding CSS Custom Property names.
|
|
330
|
+
|
|
331
|
+
## Type aliases
|
|
332
|
+
|
|
333
|
+
### ThemeChangeListener
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
export type ThemeChangeListener<TSchema extends Record<string, string> = Record<string, string>> = (detail: ThemeChangeDetail<TSchema>) => void;
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
In-process callback registered with `Theme.subscribe()` for applied theme changes.
|
|
340
|
+
|
|
341
|
+
### ThemeChangeSource
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
export type ThemeChangeSource = "init" | "set" | "toggle" | "clearPreference" | "system";
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Reason a theme change notification was emitted.
|
|
348
|
+
|
|
349
|
+
### ThemeClassResolver
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
export type ThemeClassResolver<TSchema extends Record<string, string> = Record<string, string>> = (theme: ThemeDefinition<TSchema>) => string;
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Resolves the single DOM class token applied for a theme when custom class application is enabled.
|
|
356
|
+
|
|
357
|
+
## Variables
|
|
358
|
+
|
|
359
|
+
### DARK_THEME
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
export declare const DARK_THEME: ThemeDefinition;
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Built-in dark theme used by default and available for custom theme lists.
|
|
366
|
+
|
|
367
|
+
### LIGHT_THEME
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
export declare const LIGHT_THEME: ThemeDefinition;
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Built-in light theme used by default and available for custom theme lists.
|
|
374
|
+
|
|
375
|
+
### THEME_CHANGE_EVENT
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
export declare const THEME_CHANGE_EVENT = "themechange";
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Window event name dispatched with `ThemeChangeDetail` after a theme change is applied in browser environments.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: SSR, Pre-Paint, and Tailwind
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# SSR, Pre-Paint, and Tailwind
|
|
6
|
+
|
|
7
|
+
## Preventing a flash of unstyled content
|
|
8
|
+
|
|
9
|
+
A client-side `init()` call runs after the page has already painted once, which is visible as a flash from the default theme to the resolved one. `getPrePaintScript()` — available as a method on an existing manager, or as the standalone `getPrePaintScript(options)` export when no manager exists yet — returns a synchronous, minified inline IIFE string that duplicates the same stored-preference-then-system-preference resolution, and applies the configured attribute, classes, and any static tokens before the browser paints:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { getPrePaintScript } from "@codenhub/theme";
|
|
13
|
+
|
|
14
|
+
const script = getPrePaintScript({ storageKey: "app-theme-preference" });
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Inject the returned string into a blocking `<script>` in the document `<head>`, before any stylesheet or content that depends on the theme:
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<head>
|
|
21
|
+
<script>
|
|
22
|
+
/* inline the string returned by getPrePaintScript() here */
|
|
23
|
+
</script>
|
|
24
|
+
</head>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Pass the same `themes`, `tokenSchema`, `defaultTheme`, `systemTheme`, `storageKey`, `attribute`, `shouldApplyClass`, and `isTailwindCss` options used to construct the manager, so the pre-paint script and the later `init()` call resolve to the same theme. `getPrePaintScript()` only serializes **static** tokens from each theme's `ThemeDefinition.tokens` — and only when `tokenSchema` is passed to it — runtime overrides passed to `init(tokens)` are applied after hydration, during client-side execution, and cannot be part of a script that runs before any application code.
|
|
28
|
+
|
|
29
|
+
## Server-side rendering
|
|
30
|
+
|
|
31
|
+
Without browser APIs, `Theme` methods skip storage reads/writes, DOM updates, and listener registration; `getSystem()` falls back to `defaultTheme`. Every method call remains safe to make during SSR — nothing throws for a missing `window`, `document`, or `localStorage` — but the package cannot produce server-rendered HTML attributes on its own. To avoid a flash on a server-rendered page, either:
|
|
32
|
+
|
|
33
|
+
- Render the server markup with the same attribute/class the pre-paint script would apply (requiring the server to read the same cookie or header a real theme service would use), or
|
|
34
|
+
- Rely on the pre-paint script above to correct the DOM before first paint, accepting that server-rendered markup itself does not carry theme state.
|
|
35
|
+
|
|
36
|
+
Keep any server-side resolution logic — stored names, system mapping, storage key, attribute, classes, color scheme — aligned with the options passed to `createTheme()` and `getPrePaintScript()`; a mismatch reintroduces the flash it exists to prevent.
|
|
37
|
+
|
|
38
|
+
## Tailwind's `dark` class
|
|
39
|
+
|
|
40
|
+
Set `isTailwindCss: true` to toggle Tailwind CSS's `dark` class on `document.documentElement` alongside the configured attribute, for every theme whose `colorScheme` is `"dark"`. This is independent of `shouldApplyClass` (below) — a Tailwind app commonly wants both the `dark` class for Tailwind's `dark:` variant and, when `shouldApplyClass` is left at its default, a `theme-${name}` class for custom per-theme CSS.
|
|
41
|
+
|
|
42
|
+
## Classes and cleanup
|
|
43
|
+
|
|
44
|
+
`shouldApplyClass` controls whether and how a class is applied to `document.documentElement` alongside the attribute: `true` (the default) applies `theme-${name}`, `false` applies no class, and a `ThemeClassResolver` function receives the active `ThemeDefinition` and returns one custom class token — throwing if it returns an empty or whitespace-containing string.
|
|
45
|
+
|
|
46
|
+
`destroy(options?)` removes the media-query and storage listeners and clears in-process subscribers and runtime tokens, leaving the instance safe to `init()` again. By default it does **not** touch the DOM or stored preference. Pass `{ revertDom: true }` to also remove the configured attribute, classes, and CSS custom properties from `document.documentElement` — useful when unmounting a scoped theme manager that should leave no trace, such as in a component test or a preview pane.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tokens, Persistence, and Cross-Tab Sync
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Tokens, Persistence, and Cross-Tab Sync
|
|
6
|
+
|
|
7
|
+
## Token resolution
|
|
8
|
+
|
|
9
|
+
`tokenSchema` maps typed token names to the CSS custom property each one writes, such as `{ primary: "--color-primary" }`. Once configured, `get()` returns the active `ThemeDefinition` with every schema key resolved from three sources, in this priority order (last wins):
|
|
10
|
+
|
|
11
|
+
1. **Computed style** — for a token not defined in JS, the value is read from `window.getComputedStyle(document.documentElement)` in browser environments.
|
|
12
|
+
2. **Theme static tokens** — values set on the active theme's own `ThemeDefinition.tokens`.
|
|
13
|
+
3. **Runtime overrides** — values passed to `init()`, `set()`, or `toggle()`.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
const theme = createTheme({
|
|
17
|
+
themes: [{ name: "brand", colorScheme: "light", tokens: { accent: "#ff6600" } }],
|
|
18
|
+
tokenSchema: { accent: "--color-accent" },
|
|
19
|
+
}).init();
|
|
20
|
+
|
|
21
|
+
theme.set("brand", { accent: "#00c853" }); // runtime override wins over the static "#ff6600"
|
|
22
|
+
theme.get().tokens?.accent; // "#00c853"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Reading computed values can force a synchronous layout reflow. Pass `{ skipComputed: true }` to `get()` to skip that source and avoid the reflow when only the JS-known values are needed.
|
|
26
|
+
|
|
27
|
+
Runtime overrides persist across subsequent theme changes until explicitly replaced — pass a new object, including `{}`, to `init()`, `set()`, or `toggle()` to clear or replace them. Passing a token key that is not in `tokenSchema` throws.
|
|
28
|
+
|
|
29
|
+
## Persistence
|
|
30
|
+
|
|
31
|
+
An explicit preference set through `set()` or `toggle()` is written to `localStorage` under `storageKey` (default `"app-theme-preference"`). `getStored()` returns that value when it names a currently configured theme, or `null` when it is unset, invalid, or storage is unavailable (for example during SSR). `clearPreference()` removes the stored value and re-applies whichever theme `getSystem()` currently resolves to.
|
|
32
|
+
|
|
33
|
+
Storage reads, writes, and removals that throw — a full quota, a disabled storage API, a private-browsing restriction — are logged with `console.error` and treated the same as unavailable storage; they never throw out of `Theme` methods.
|
|
34
|
+
|
|
35
|
+
## Cross-tab sync
|
|
36
|
+
|
|
37
|
+
Every tab running `createTheme().init()` against the same `storageKey` stays in sync: a `storage` event fires in every other tab when one tab writes a new preference, and each one applies the change if it names a valid configured theme. A tab with its own runtime token overrides keeps them — only the active theme name changes from cross-tab sync, not tokens passed in-process.
|
|
38
|
+
|
|
39
|
+
The OS `prefers-color-scheme` media query applies automatically only while no valid stored preference exists. Once a tab (or a previous session) has stored an explicit preference, later system-preference changes are ignored until `clearPreference()` runs.
|
|
40
|
+
|
|
41
|
+
## Reacting to changes
|
|
42
|
+
|
|
43
|
+
`subscribe(listener)` registers a callback invoked after every applied change — from `init()`, `set()`, `toggle()`, `clearPreference()`, or an accepted `system` or cross-tab update — and returns an unsubscribe function:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const unsubscribe = theme.subscribe(({ name, source }) => {
|
|
47
|
+
console.log(`theme is now "${name}" (${source})`);
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
A subscriber that throws is logged and does not stop other subscribers from running. Browsers also receive a `window` `CustomEvent` named by the exported `THEME_CHANGE_EVENT` constant (`"themechange"`) carrying the same detail, for code that prefers DOM events over `subscribe()`.
|