QUESTPIE
AdminConfiguration

Theming

Colours, fonts and radius are CSS variables, not config. Override them in one stylesheet, mirror two selectors, and the panel is yours.

View markdown

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.

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.

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.

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.

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.

TokenWhat it moves
--primary, --primary-foregroundSolid primary buttons and their text
--ringThe focus ring
--background, --foregroundThe page under everything
--card, --popover, --muted, --accentPanels, floating surfaces, hovers
--destructive, --success, --warningThe semantic states
--sidebar, --sidebar-active-backgroundThe left rail, themed on its own
--radius, --surface-radiusCorner rounding, base and on cards
--control-radius, --control-heightInputs and buttons
--font-sans, --font-headingBody text, and headings
--font-chrome, --font-monoButtons and labels, and code or ids
--spacing-card, --spacing-sectionPadding 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.

// 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} ... />

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.

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.

FileReplaces
admin-sidebar-brand.tsxThe logo and name block at the top of the rail
admin-sidebar-nav-item.tsxHow one navigation row renders
admin-auth-layout.tsxThe 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.

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.

Next

Collections and globals is the layer under the paint. Labels, icons, which columns a list shows, and how a form is arranged.

On this page