ThemeProvider and dark mode with AtroUI and next-themes
Wire dark mode for AtroUI registry components: class strategy, default dark, and tokens so dark-first styles actually apply.
AtroUI’s tokens assume a class-based dark theme. Whether you use next-themes or your own provider, .dark on the html element is what makes CSS variables resolve correctly.
Skip the provider, or use the wrong attribute, and you get a half-themed app: components render, but backgrounds and brand colors miss the dark sheet.
Install the peer if needed
If you add @atroui/theme-toggle or similar, the CLI may pull next-themes. Install it if your layout does not already have it.
npm install next-themesRecommended provider setup
Use attribute="class", defaultTheme="dark", and enableSystem if you want OS preference to win when the user has not chosen. suppressHydrationWarning on <html> avoids the classic theme flash warning.
import { ThemeProvider } from "next-themes"
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className="dark" suppressHydrationWarning>
<body className="min-h-screen bg-background text-foreground antialiased">
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
)
}Fonts and the dark class
Load Outfit with variable: "--font-outfit" and put that variable on <html> so display styles match the catalog. You can keep className="dark" on html for first paint while ThemeProvider manages the class afterward.
Full layout snippet: Install AtroUI in Next.js.
Tokens still come from globals
ThemeProvider toggles the class. atroui/globals.css defines what .dark means. Import globals once. Override --brand and neutrals in your CSS when you re-skin. See dark-first tokens and Theming.
Common mistakes
Forgetting next-themes in package.json.
Using data-theme when tokens expect .dark.
Importing globals in a leaf component instead of the root layout.
Expecting light-first defaults. AtroUI is dark-first; light is the alternate.