Theming
Every noiceui component reads from CSS variables. Recolor the whole library by changing tokens, never components.
The token set
:root holds the light theme, .dark holds the dark one. Tailwind v4 maps them through @theme inline, so a class like bg-brand always resolves against the active theme.
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--muted: oklch(0.97 0 0);
--border: oklch(0.922 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--muted: oklch(0.269 0 0);
--border: oklch(1 0 0 / 10%);
}Your brand color
The accent is --brand, paired with --brand-foreground for anything sitting on top of it. These are separate from --primary, which stays a neutral ink for ordinary buttons. Change the pair in both themes, and check the foreground clears 4.5:1 against the fill.
:root {
--brand: oklch(0.45 0.14 19); /* burgundy */
--brand-foreground: oklch(0.985 0.01 30);
}
.dark {
--brand: oklch(0.68 0.13 17); /* lifted for dark */
--brand-foreground: oklch(0.25 0.06 16);
}The same colour appears in components that need it in JavaScript, such as the sparkline stroke and the spotlight glow. They read the token too, so one edit reaches all of them.
Radius
One value sets the feel of every surface. rounded-sm, rounded-md and rounded-lg are all derived from it, so a single edit reshapes the library at once.
:root {
--radius: 0.375rem; /* sharper */
/* or */
--radius: 1rem; /* softer */
}Dark mode
Dark mode is class-based, so it can be toggled at runtime. Add the variant to your stylesheet, then wrap your app once.
@custom-variant dark (&:where(.dark, .dark *));// app/layout.tsx
<html lang="en" suppressHydrationWarning>
<head>
{/* Applies the stored theme before first paint. */}
<script dangerouslySetInnerHTML={{ __html: noFlashScript }} />
</head>
<body>
<ThemeProvider defaultTheme="system">{children}</ThemeProvider>
</body>
</html>The head script matters. Without it the page paints light, then snaps to dark once React hydrates. Both it and the provider are exported from @/components/theme-provider.
Per-component overrides
Every part accepts className. Classes are merged with tailwind-merge, so your value wins over the default without !important.
<Button className="rounded-full bg-brand text-brand-foreground">
Rounded brand button
</Button>