# Theming (/docs/admin/configuration/theming)

---
title: Theming
description: Colours, fonts and radius are CSS variables, not config. Override them in one stylesheet, mirror two selectors, and the panel is yours.
kind: guide
package: "@questpie/admin"
---

How do you get your brand colour into the panel? Not through `config/admin.ts`.
That file holds the name and the logo. Everything visual is CSS.

## One stylesheet

`create-questpie` writes an admin stylesheet and links it from the admin route.
TanStack Start puts it at `src/admin.css`, Next at `src/app/admin/admin.css`.
This is the whole file before you add anything.

```css title="src/admin.css"
@import "tailwindcss";
@import "@questpie/admin/client/styles/index.css";

@source "../node_modules/@questpie/admin/";
```

`index.css` is a one-line alias for `base.css`, so importing either one works.
`base.css` carries the tokens, the Tailwind mappings and the fonts.
`@source` points Tailwind at the package so it keeps the classes the admin uses.
The shipped examples add a second `@source` one directory further up, to cover
an install that hoists the package above the app.

## Override the tokens

Put your rules after the import and mirror the two selectors `base.css` uses.
Dark values go on `:root, .dark`. Light values go on `.light, :root.light`.
Dark is the default, so `:root` alone is a dark rule.

```css title="src/admin.css"
:root,
.dark {
	--primary: oklch(0.78 0.18 25);
	--ring: oklch(0.78 0.18 25);
	--font-heading: "Caveat Brush", system-ui, sans-serif;
}

.light,
:root.light {
	--primary: oklch(0.65 0.2 25);
	--ring: oklch(0.65 0.2 25);
	--font-heading: "Caveat Brush", system-ui, sans-serif;
}
```

Reload. Primary buttons and focus rings carry the new hue, and headings pick up
the new family. Load the webfont yourself, up beside the imports at the top of
the file. Nothing in `config/admin.ts` moved. Lighten the colour in the dark
block, as above, so it still reads on a dark surface.

<Callout type="warn" title="A plain `.light` override silently loses">
	`base.css` writes light tokens on `:root.light`, which is more specific than a
	single class. Your `.light { … }` rule never wins. The pairing above matches
	that specificity, so source order decides, and yours is later.
</Callout>

The dark block is easier. `base.css` writes it on `:root, .dark`, so a plain
`:root` rule of yours ties on specificity and wins on source order. Only the
light block needs the pairing.

## The tokens worth knowing

There are more. `base.css` opens with a reference comment naming most of them.

| Token                                        | What it moves                               |
| -------------------------------------------- | ------------------------------------------- |
| `--primary`, `--primary-foreground`          | Solid primary buttons and their text        |
| `--ring`                                     | The focus ring                              |
| `--background`, `--foreground`               | The page under everything                   |
| `--card`, `--popover`, `--muted`, `--accent` | Panels, floating surfaces, hovers           |
| `--destructive`, `--success`, `--warning`    | The semantic states                         |
| `--sidebar`, `--sidebar-active-background`   | The left rail, themed on its own            |
| `--radius`, `--surface-radius`               | Corner rounding, base and on cards          |
| `--control-radius`, `--control-height`       | Inputs and buttons                          |
| `--font-sans`, `--font-heading`              | Body text, and headings                     |
| `--font-chrome`, `--font-mono`               | Buttons and labels, and code or ids         |
| `--spacing-card`, `--spacing-section`        | Padding inside a card, gap between sections |

## Light and dark

The admin manages the mode itself. `AdminLayoutProvider` toggles `.dark` or
`.light` on `<html>` and sets `color-scheme` to match. The default is `system`,
which follows the operating system.

The user menu in the sidebar footer holds a switcher with three choices: light,
dark and system. The choice persists in localStorage under
`questpie:admin-theme`. Pass `showThemeToggle={false}` to take the switcher
away.

```tsx
// Sets the mode the panel opens in. The switcher can still change it.
<AdminLayoutProvider theme="dark" ... />

// Pass both and the mode is yours. The switcher writes to your state.
<AdminLayoutProvider theme={theme} setTheme={setTheme} ... />
```

<Callout type="warn" title="Never wrap the admin in a dark-class div">
	Dialogs, popovers, selects and toasts render through portals into
	`document.body`, outside your wrapper. They follow the class on `html` and
	come out in the opposite theme. Pass `theme` on the provider instead. It sets
	the class at the root, where the portals can see it.
</Callout>

## Replacing chrome

Three parts of the shell take a component of yours instead of a stylesheet. Put
the file in `src/questpie/admin/components/` and codegen picks it up by name.

| File                         | Replaces                                       |
| ---------------------------- | ---------------------------------------------- |
| `admin-sidebar-brand.tsx`    | The logo and name block at the top of the rail |
| `admin-sidebar-nav-item.tsx` | How one navigation row renders                 |
| `admin-auth-layout.tsx`      | The shell around login, setup and invitations  |

The brand component receives `name` and `collapsed`. The nav item receives
`item`, `isActive` and `collapsed`. Both prop types are exported from
`@questpie/admin/client`, as `AdminSidebarBrandProps` and
`AdminSidebarNavItemProps`.

```tsx title="src/questpie/admin/components/admin-sidebar-brand.tsx"
import type { AdminSidebarBrandProps } from "@questpie/admin/client";

export default function Brand({ name, collapsed }: AdminSidebarBrandProps) {
	return <span>{collapsed ? name.slice(0, 1) : name}</span>;
}
```

For a whole screen rather than a piece of chrome, register a view or a page
instead. See [Custom views](/docs/admin/custom-views).

## Next

**[Collections and globals](/docs/admin/collections)** is the layer under the
paint. Labels, icons, which columns a list shows, and how a form is arranged.
