Skip to content

Appearance

The desktop app's runtime theme as a React provider. Themes, light and dark pairing, contrast, accent, fonts, the code theme and the motion level, kept by a host.

zidane-ui/appearance is the desktop app's runtime theme. AppearanceProvider reads the user's preferences from an AppearanceHost, picks the active theme (the OS's appearance under system), derives its palette and writes the --zg-* variables on <html>, with the contrast, the accent override and the fonts. It also puts the tree under the motion preference, and stores a snapshot of the variables so the next page load paints the right theme before any script runs. Each frame below runs on its own fixture host, scoped to the frame.

Live: change the mode or the contrast
Light
Another dark theme with an accent override
High contrast (160)
Larger font sizes

Mount it#

Wrap the app once. In a web app, the preferences live in localStorage.

TSX
import 'zidane-ui/styles.css'
import { createBrowserAppearanceHost } from 'zidane-ui/adapters'
import { AppearanceProvider, codeThemeTypeOf } from 'zidane-ui/appearance'

const appearance = createBrowserAppearanceHost({ codeThemeType: codeThemeTypeOf })

export function Root() {
  return (
    <AppearanceProvider host={appearance}>
      <App />
    </AppearanceProvider>
  )
}

AppHostProvider (zidane-ui/app) puts host.appearance in an AppearanceProvider for you.

PropDoes
hostWhere the preferences live, the extra themes, the snapshot and the window effects
folder, themesKeyThe folder whose extra themes are offered (host.themes), and a key that changes when they may have changed
legacyThemeIdA theme id from zidane's shared settings, read once on the first run to seed the pairing
rootWhere the variables go. Default <html>. Anywhere else, no snapshot is written and the window is left alone
motionPut the tree under the motion preference (MotionProvider). Default on

Pick a host#

HostFromKeeps
createBrowserAppearanceHost()zidane-ui/adapterslocalStorage, under the desktop app's keys. Another tab's change is adopted. zidane-server keeps no appearance, so the web app offers the built-in themes only.
createFixtureAppearanceHost(options)zidane-ui/appearance/testingMemory, for demos and tests. It records the snapshot and the window calls.
Your ownzidane-ui/appearance/headlessAnything. createStorageAppearancePrefs and createStorageBoot keep the preferences and the snapshot in any Storage.

What a host offers#

Only prefs is required.

MemberGives
prefsThe preferences: get, subscribe, set, reset, isFirstRun
themes(folder)Themes beyond the built-in ones (the desktop app lists the folder's extension themes)
boot.write(snapshot)Stores the applied variables for the pre-paint script
window.setBackground, window.setZoom, window.setNativeThemeNative effects: the resize background, the interface scale, the title bar's appearance

The snapshot is { appearance, vars } under zg:appearance:boot:v1. A page replays it before its bundle loads:

html
<script>
  try {
    const s = JSON.parse(localStorage.getItem('zg:appearance:boot:v1'))
    for (const [k, v] of Object.entries(s.vars)) document.documentElement.style.setProperty(k, v)
    document.documentElement.classList.add(s.appearance)
  } catch {}
</script>

Read it#

HookReturns
useAppearance()The preferences, the active appearance and theme, every selectable theme, and the setters (set, setMode, pickTheme, reset)
useAppearanceState()The preferences alone
useTheme(), useColors(), usePalette()The active theme, its colors, the derived palette
useShikiTheme()The code theme: the pick for the current appearance, else the theme's own
useAppearanceMotion(), useAppearanceReducedMotion()The motion level the preference resolves to

Outside a provider the hooks return the defaults and the setters do nothing. parseAppearance validates a stored blob field by field, LIMITS holds the bounds, fontsToCssVars and availableCuratedFonts are the font tokens and the installed-font check, and codeThemesOf(appearance) lists the bundled code themes.

Test your host#

TypeScript
import { describeAppearanceHostContract } from 'zidane-ui/appearance/testing/contract'

describeAppearanceHostContract('my host', backend => makeHarness(backend), ['boot'])
Entry pointHolds
zidane-ui/appearanceAppearanceProvider, the hooks, applyPalette, codeThemesOf
zidane-ui/appearance/headlessAppearanceHost, the preferences model, the Storage stores, the font tokens, zoomKeyCommands(prefs) (the zoom keys for useKeyCommands)
zidane-ui/appearance/testingcreateFixtureAppearanceHost
zidane-ui/appearance/testing/contractdescribeAppearanceHostContract