Theming
Change the theme tokens.
How the system works
The theme has two layers. Values are CSS variables in globals.css, named for what a colour is for, such as --card. Utilities map each variable to a class in @theme inline, so --card becomes bg-card. Components use only the utilities, so changing a value restyles every component without touching its file.
| Variable | Utility | Read by |
|---|---|---|
--background#fff | bg-background | Page canvas |
--primary#0090ff | bg-primary text-primary-foreground | Filled buttons, switches, checks, selection |
--border#00000017 | border-border | Every divider and edge |
--radius0.5rem | rounded-sm … rounded-3xl | All corners |
--shadow-defaulthairline + 1px drop | shadow-raised | Resting cards and controls |
--text-ui0.8125rem | text-ui | Buttons, inputs, menus, nav |
Dark mode works the same way. .dark on <html> points the same variables at dark values, so a component written once is right in both modes. The accent is eight of those variables, --primary to --ring, which a preset swaps together.
- Map each modePut light values on
:rootand dark values on.dark, under the same names. - Use semantic utilitiesWrite
bg-cardortext-muted-foreground, never palette colours or hex. - Keep it centralSkip
dark:colour overrides in components, so a theme change stays one edit in one file.
The same file also holds these tokens.
--radiusOne base that every corner multiplies, so nested corners stay concentric.text-ui-*The type scale for text inside components.--shadow-*Four shadow levels behind shadow-xs to shadow-xl and the named elevation.--font-*The UI, code and reading font stacks.--shellColours for app chrome around the content panel.--scrollbarScrollbar thumb colours.
pnpm dlx shadcn@latest add @paneui/theme@paneui/theme writes the variables below and installs @paneui/utils, whose cn() merges the text-ui-* sizes as font sizes. For a tinted wash, use the -subtle token, such as bg-success-subtle.
Colors
Colours are named by role, and each surface has a matching -foreground token for text on it. Use tokens in components, never palette colours (bg-zinc-100) or hex values. Every token's light and dark value is on Customize.
- background
- card
- surface-sunken
- surface-standout
- primary
- primary-subtle
- secondary
- muted-foreground
- fg-tertiary
- border
- border-strong
- ring
- destructive
- success
- warning
- chart-1…8
- shell
Dark mode
Put .dark on <html> to switch every token. The theme declares the variant as @custom-variant dark (&:is(.dark *)). Set color-scheme at the same time so native scrollbars and form controls follow. Pair primary with text-primary-foreground, never text-white, because the neutral preset makes it near-white in dark mode.
To let users switch, use Theme Toggle, which stores the choice for the script above.
Accent presets
The accent is eight role variables per mode, blue by default: primary and its -hover, -active, -foreground, -subtle, -subtle-hover and -border variants, plus ring. Every preset reads the same steps of its own 12-step scale, so surfaces, text and charts stay the same. Install one after the theme.
- theme-neutral
- theme-bluedefault
- theme-green
- theme-orange
- theme-rose
- theme-violet
pnpm dlx shadcn@latest add @paneui/theme-violetAn installed preset overwrites the variables on :root and .dark, so the accent is fixed. To let users switch, keep the default roles on :root, add one block per accent keyed on an attribute, and apply the saved choice in the same pre-paint script as dark mode.
For a brand colour, use the picker on Customize to derive the eight roles and copy them.
Typography
Use the text-ui* sizes inside components, and keep text-sm to text-2xl for reading copy and page headings. Set titles in font-semibold, labels and buttons in font-medium, and use font-semimedium (450) where 400 reads too light on a dense row.
| Class | Size | Use for |
|---|---|---|
| text-ui-2xs | 10px | Tiny counters and labels. |
| text-ui-xs | 11px | Meta text, shortcuts and counts. |
| text-ui-sm | 12px | Captions, badges, small controls, tooltips. |
| text-ui | 13px | The default for controls, menus and text inside components. |
| text-ui-lg | 14px | Large controls and card titles. |
Fonts
The theme names its faces in --font-sans, --font-mono and --font-serif, each with a CJK face and the system stack behind it. Without the font files it renders in system fonts, so installing them is optional.
| Utility | Face | Use for |
|---|---|---|
| font-sans | Inter, Noto Sans SC | All UI text. Inter's ss03 set is on. |
| font-mono | JetBrains Mono | Code, keys, terminals and tabular IDs. |
| font-serif | Newsreader, Noto Serif SC | Long-form reading (the reading typeset preset). |
pnpm dlx shadcn@latest add @paneui/fontsThis adds the font packages and packages/tokens/styles/fonts.css; import that file once in your root layout. Each face is split by unicode range, so a page downloads only the glyph sets it shows. For code that lines up with Chinese text, install a CJK-aligned monospace such as Sarasa Gothic or Maple Mono yourself and put it first in --font-mono.
Radius
Every step is a multiple of --radius, so change the base and the whole scale follows. No step goes past --radius-max (16px): larger corners turn controls into pills and push nested parts apart. Keep nested corners concentric, with the inner radius equal to the outer radius minus the padding between them. That is why p-1 menus (rounded-2xl) hold rounded-lg rows. Use rounded-full only for shapes that are actually round. Sizes below are at the default 0.5rem; try other values on Customize.
| Preview | Class | Size | Use for |
|---|---|---|---|
| rounded-xs | 2px | Search highlights, favicons and tiny segments. | |
| rounded-sm | 4px | Checkboxes, extra-small buttons, keys and small chips. | |
| rounded-md | 6px | Tab indicators inside a p-0.5 list. | |
| rounded-lg | 8px | Controls and menu rows. | |
| rounded-xl | 10px | Cards, tables and code blocks. | |
| rounded-2xl | 12px | Menus and popups, two-layer inset cards. | |
| rounded-3xl | 16px | Dialogs, drawers and big panels. | |
| rounded-4xl | 16px | Kept for older code; sits at the 16px cap. | |
| rounded-5xl | 16px | Kept for older code; sits at the 16px cap. |
Elevation
Four shadow levels back Tailwind's shadow-xs to shadow-xl, so plain utilities pick them up. Give resting cards, tables and code blocks shadow-raised, never a floating level. Show hover on rows, menu items and ghost buttons with a fill (bg-secondary), not a ring.
| Preview | Class | Token | Use for |
|---|---|---|---|
| shadow-xs | --shadow-border | A 1px ring, no lift. | |
| shadow-sm | --shadow-default | Resting controls and cards. | |
| shadow-lg | --shadow-lg | Menus, popovers and floating panels (shadow-md is the same). | |
| shadow-xl | --shadow-xl | Dialogs, drawers and sheets. |
Components read these named levels, each built on the scale above.
shadow-raisedResting controls and cards (reads shadow-sm). Hover steps to shadow-raised-hover.shadow-surfacePopovers, menus and floating panels (reads shadow-lg).shadow-wellInset hairline for fields and tracks.shadow-chipKeys and small chips.shadow-primaryFilled primary buttons (none in dark).Native style rules
Follow these rules when you build a new component.
secondary,mutedandaccentare fills; usebg-card,bg-popoverorbg-backgroundfor anything that must be opaque.- No gradient sheens, inset highlights, text shadows or glows.
- Size buttons and inputs at
h-8(smh-7, xsh-6) and menu rows at 26px. Show disabled withopacity-64. - Answer a press on standard controls with a fill change, not a scale. Popups enter from
scale-98and opacity. - Show focus with
focus-visible:ring-2 focus-visible:ring-ring/50. - Put app chrome on
bg-shelland the content in ashell-insetpanel.
Adding tokens
Define a new token with a value for both modes, then map it in @theme inline to get the utilities.