Themes
Style the admin with shadcn CSS variables — no palette API.
Chelone theming is shadcn theming. There is no AdminTheme object, no
palette prop, no TypeScript theme system — the look of the admin is defined by
CSS variables on your stylesheet, exactly as any shadcn project. Change a
variable, and every component that reads it updates.
The CSS variables
The admin components render in terms of the standard shadcn design tokens —
--background, --foreground, --primary, --muted, --border, --radius,
the --sidebar-* set, and so on. They live in your global CSS, scoped to the
root and to .dark:
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--radius: 0.625rem;
/* …the rest of the shadcn tokens… */
}
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
/* …dark overrides… */
}Retheme the whole admin by editing these values — recolor --primary, soften
--radius, tune the sidebar. Because it's plain CSS, any shadcn theme generator
or registry theme drops straight in.
Light / dark mode
Mode (light / dark / system) is separate from the palette. <ThemeProvider>
holds the chosen mode and applies it as a class on <html>: light or dark,
with system resolved through prefers-color-scheme. The shadcn .dark token
block and Tailwind dark: variants key off that class.
What <Admin> mounts
<Admin> wraps the shell in a <ThemeProvider> with the defaults
(defaultTheme="system", storageKey="theme"), so the <ThemeModeToggle> in
the default <AppBar> works with no setup. <Admin> has no props for either
option. To change them, mount your own <ThemeProvider> above <Admin>; it
stays in charge and <Admin> mounts none. <ThemeProvider> reads the store from
the nearest <CheloneProvider>, so it needs one above it:
import { CheloneProvider, Admin, ThemeProvider } from "@chelone/react";
<CheloneProvider
dataProvider={dataProvider}
queryClient={queryClient}
store={store}
>
<ThemeProvider defaultTheme="dark" storageKey="my-app-theme">
<Admin dataProvider={dataProvider} queryClient={queryClient} store={store}>
{/* … */}
</Admin>
</ThemeProvider>
</CheloneProvider>;Persistence
- With a store, the mode is saved in the store under
storageKey. Which store: the<ThemeProvider>uses the nearest<CheloneProvider>'s. The default provider<Admin>mounts uses<Admin store>. With your own provider above<Admin>, pass the samestoreto the<CheloneProvider>, as above; otherwise the mode goes tolocalStorage. - Without a store, it is saved in
localStorageunderstorageKey. IflocalStorageis blocked or full, the mode still applies for the session. - Nothing saved yet:
defaultThemeis used. A stored value other thanlight,darkorsystemis ignored, in the store and inlocalStorage.
Reading and setting the mode
import { useResolvedTheme, useTheme } from "@chelone/react";
const [mode, setMode] = useTheme(); // "light" | "dark" | "system"
setMode("dark");
const resolved = useResolvedTheme(); // "light" | "dark", never "system"useTheme()returns a[mode, setMode]tuple. Without a<ThemeProvider>above,modeis"system"andsetModedoes nothing.useResolvedTheme()returns the concrete mode:"system"becomes"dark"or"light"fromprefers-color-scheme, and"light"wherematchMediais missing.
Distributing a theme
Because a theme is just CSS variables, you ship it the shadcn way — as a registry
item with cssVars, installed via the CLI — not as a prop. There is no
theme= prop to pass to <Admin>.
Tips
--radiuscascades through the whole component set — one value controls the corner rounding everywhere.- Keep mode (light/dark, a
ThemeProviderconcern) separate from palette (CSS variables, a stylesheet concern); they compose but aren't the same knob.