Switching themes
The theme contract
Section titled “The theme contract”Two themes are interchangeable when they define the same keys. Your app references keys, so any theme with the same keys renders it.
The bundled themes all satisfy ThemeContract, the keys of the library defaults. Use it to check your own themes:
import { defineConfig } from 'yarcl/define';import defaults from 'yarcl/defaults';import type { ThemeContract } from 'yarcl/themes';
export const midnight = defineConfig({ ...defaults, colors: { … }, sizes: { … },}) satisfies ThemeContract;A missing key (say, no lg size) is a type error, so the theme can’t silently break a screen that uses size="lg". Extra keys are allowed.
For your own design system, define your own contract the same way: a type listing the keys your app uses.
At build time
Section titled “At build time”The simplest approach: one theme per build. Point the config file at the theme you want:
export { compact as default } from 'yarcl/themes';To build the same app with several themes (white-label products, per-customer builds), keep one config file per theme and choose it with an environment variable in the plugin:
yarcl({ config: `src/themes/${process.env.THEME ?? 'default'}.ts` })Remember the matching paths entry in tsconfig.json; with a shared contract, any of the theme files works for type-checking.
At runtime
Section titled “At runtime”applyTheme from yarcl/css switches the whole app to another theme without a rebuild:
import { applyTheme, resetTheme } from 'yarcl/css';import { themes } from 'yarcl/themes';
applyTheme(themes.editorial);// laterresetTheme(); // back to the build-time configIt does two things:
- Replaces the styles. It runs
generateCss, the same generator the Vite plugin uses at build time, and puts the result in a<style>element that overrides the build-time stylesheet. Dialogs, menus and toasts switch too. - Switches the defaults. Components read
defaults, per-componentcomponentsdefaults and heading levels from the applied theme, and re-render. A theme withcomponents: { Button: { radius: 'rounded' } }gets pill buttons, and one withdefaults: { density: 'compact' }gets compact tables.
The theme must define the keys your app uses, because prop types still come from the build-time config. The bundled themes share the defaults’ keys (the contract above), so any of them can replace another.
Pass onWarning to receive contrast warnings, and use useConfig() from yarcl when your own components need the active theme’s values:
applyTheme(theme, { onWarning: (message) => console.warn(message) });This is how the theme playground and the demo on the home page work. generateCss is exported from yarcl/css as well, if you only need the stylesheet.
Light and dark
Section titled “Light and dark”Every theme has light and dark values built in. The page follows the OS setting, and color-scheme switches it:
document.documentElement.style.colorScheme = 'dark';Theming a region
Section titled “Theming a region”Everything is a CSS variable, so a region can override values without a new theme:
.promo { --yarcl-color-primary: light-dark(#be185d, #f9a8d4); --yarcl-color-primary-on: #fff; --yarcl-focus-color: var(--yarcl-color-primary);}