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:

src/index.css
: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 same store to the <CheloneProvider>, as above; otherwise the mode goes to localStorage.
  • Without a store, it is saved in localStorage under storageKey. If localStorage is blocked or full, the mode still applies for the session.
  • Nothing saved yet: defaultTheme is used. A stored value other than light, dark or system is ignored, in the store and in localStorage.

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, mode is "system" and setMode does nothing.
  • useResolvedTheme() returns the concrete mode: "system" becomes "dark" or "light" from prefers-color-scheme, and "light" where matchMedia is 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

  • --radius cascades through the whole component set — one value controls the corner rounding everywhere.
  • Keep mode (light/dark, a ThemeProvider concern) separate from palette (CSS variables, a stylesheet concern); they compose but aren't the same knob.