105 lines
3.3 KiB
Plaintext
105 lines
3.3 KiB
Plaintext
---
|
|
title: Set up appearance preferences
|
|
description: Aggregate appearance definitions and effects with the application's shared preferences.
|
|
order: 1
|
|
toc:
|
|
- id: provider
|
|
title: Aggregate preferences
|
|
- id: effects
|
|
title: Register effects
|
|
- id: theme-style
|
|
title: Inject theme variables
|
|
- id: persistence
|
|
title: Persist updates
|
|
---
|
|
|
|
## Aggregate preferences {#provider}
|
|
|
|
Build one complete preference definition map at the application boundary. Spread definitions from appearance, locale, keyboard, and other feature packages into this map, then create or load the initial values.
|
|
|
|
Import Appearance after UI in the application stylesheet. No UI component changes are required:
|
|
|
|
```css
|
|
@import "@workspace/ui/globals.css";
|
|
@import "@workspace/appearance/globals.css";
|
|
```
|
|
|
|
```tsx
|
|
import {
|
|
createDefaultPreferences,
|
|
PreferencesProvider,
|
|
} from "@workspace/preferences"
|
|
import {
|
|
appearancePreferenceDefinitions,
|
|
appearancePreferenceEffect,
|
|
} from "@workspace/appearance"
|
|
|
|
const preferenceDefinitions = {
|
|
...appearancePreferenceDefinitions,
|
|
...localePreferenceDefinitions,
|
|
}
|
|
|
|
const initialPreferences = createDefaultPreferences(preferenceDefinitions)
|
|
const preferenceEffects = [
|
|
appearancePreferenceEffect,
|
|
localePreferenceEffect,
|
|
] as const
|
|
|
|
export function Root({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<PreferencesProvider
|
|
effects={preferenceEffects}
|
|
initialPreferences={initialPreferences}
|
|
>
|
|
{children}
|
|
</PreferencesProvider>
|
|
)
|
|
}
|
|
```
|
|
|
|
`defaultAppearancePreferences` remains available when only the appearance slice is needed, but the definition map is the preferred source when several packages are combined.
|
|
|
|
## Register effects {#effects}
|
|
|
|
`appearancePreferenceEffect` updates document classes, root attributes, and theme variables before paint, then watches operating-system theme and motion preferences after paint. Register it in the stable `effects` array passed to `PreferencesProvider`.
|
|
|
|
## Inject theme variables {#theme-style}
|
|
|
|
Use `buildThemeStyleSheet` to generate light and dark CSS variables from one base color, one accent color, and a color-vision mode. Keep the stable element ID so the appearance effect can update the same node when preferences change.
|
|
|
|
```tsx
|
|
import {
|
|
buildThemeStyleSheet,
|
|
THEME_STYLE_ELEMENT_ID,
|
|
} from "@workspace/appearance"
|
|
|
|
;<style
|
|
id={THEME_STYLE_ELEMENT_ID}
|
|
dangerouslySetInnerHTML={{
|
|
__html: buildThemeStyleSheet(
|
|
{ baseColor, accentColor },
|
|
colorVisionMode,
|
|
brightness
|
|
),
|
|
}}
|
|
/>
|
|
```
|
|
|
|
Assistance modes adjust chart and destructive semantic colors and set `data-ui-color-vision`. They are not color-vision simulators and do not transform photos, video, or brand assets.
|
|
|
|
Text scaling changes the root font size through `data-ui-text-scale`; reduced motion shortens global animation through `data-ui-motion` while preserving low-motion loading feedback; brightness adjusts theme surface variables without using `filter: brightness()`.
|
|
|
|
## Persist updates {#persistence}
|
|
|
|
The shared `onPreferenceChange` receives a typed `{ key, value }` only when a value actually changes. Forward it to a server function or cookie writer owned by the application.
|
|
|
|
```tsx
|
|
<PreferencesProvider
|
|
effects={preferenceEffects}
|
|
initialPreferences={preferences}
|
|
onPreferenceChange={(update) => savePreference(update)}
|
|
>
|
|
{children}
|
|
</PreferencesProvider>
|
|
```
|