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.

css
: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.

css
: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.

css
: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.

css
@custom-variant dark (&:where(.dark, .dark *));
tsx
// 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.

tsx
<Button className="rounded-full bg-brand text-brand-foreground">
 Rounded brand button
</Button>