Theming
Change the brand hue, the neutral tint, the radius or the mode — globally or for a single subtree — without touching a component.
Every prfct component reads its colors, radii and shadows from CSS custom properties. Theming means changing those properties; components never need to be edited. Try it live in the theme builder.
Same components, same classes — the subtree sets the mode.
Same components, same classes — the subtree sets the mode.
Light and dark
prfct ships both modes. The default setup uses next-themes to follow the operating system, persist a choice, and avoid a flash of the wrong theme:
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
)
}Scoped themes
Modes are defined on classes, not only on the root, so any subtree can switch:
<aside className="dark">…</aside> {/* a dark panel on a light page */}
<figure className="light">…</figure> {/* a light preview on a dark page */}This is how prfct's own documentation renders light and dark swatches side by side. It works because components express every mode difference through variables — none of them use dark: utilities.
Brand color
The brand scale is generated from three numbers: hue, chroma and the lightness of the solid step. Changing them regenerates all 12 steps for both modes, with text steps re-solved for contrast.
export const themeConfig = {
gray: neutralPresets.graphite,
brand: huePresets.ultramarine, // or { hue: 150, chroma: 0.15, lightness: 0.55 }
}Run pnpm tokens to rebuild styles/tokens.css. The theme builder does the same thing in the browser and gives you the CSS to paste.
Monochrome brand
Set brand: "mono" and the brand scale follows the neutral one: checked states, progress and focus become black (or white in dark mode). It's the most restrained expression of prfct, and still accessible.
Neutral tint
The gray scale can carry a subtle hue. Seven presets ship with prfct:
Radius
:root {
--radius: 0.5rem;
}Every radius step is a multiple of --radius, so 0 gives a sharp, technical interface and 0.75rem a soft, friendly one. See Shape.
Overriding semantic tokens
For one-off brand requirements, override semantic tokens after tokens.css. Prefer mapping to a scale step over a raw color, so dark mode keeps working:
:root,
.light {
--primary: var(--brand-9); /* colored primary buttons */
--primary-foreground: var(--brand-contrast);
}
.dark {
--primary: var(--brand-9);
}