Skip to main content

Dark Mode

Adding dark mode to your Next.js app, it's a one-time process. We use next-themes and CSS variables for a seamless dark mode experience.

Step 1

Install next-themes

Install next-themes by running the following command:

npm i next-themes

Step 2

Create a theme provider

Create a theme provider component, components/theme-provider.tsx

'use client';

import { ThemeProvider as NextThemeProvider } from 'next-themes';

export function ThemeProvider({ children }: React.PropsWithChildren<{}>) {
return (
<NextThemeProvider
enableSystem
defaultTheme="system"
disableTransitionOnChange
>
{children}
</NextThemeProvider>
);
}

Step 3

Wrap your root layout

Add the ThemeProvider component to your root layout, app/layout.tsx

import { ThemeProvider } from '@/components/theme-provider';
import '@/app/globals.css';

export const metadata = {
title: 'App Name',
description: 'Write your app description',
};

export default async function RootLayout({ children }: RootLayoutProps) {
return (
<html
// 💡 Prevent next-themes hydration warning
suppressHydrationWarning
>
<body
// 💡 Prevent hydration warnings caused by third-party extensions, such as Grammarly.
suppressHydrationWarning
>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}

Step 4

Add theme-switcher

Add a theme switcher components, components/theme-switcher.tsx

'use client';

import React from 'react';
import { useTheme } from 'next-themes';
import { ActionIcon } from 'rizzui/action-icon';
import { DropdownMenu } from 'rizzui/dropdown';
import { MoonIcon, SunIcon } from '@heroicons/react/24/outline';

export function ThemeSwitcher() {
const { setTheme } = useTheme();

return (
<Dropdown>
<Dropdown.Trigger>
<ActionIcon variant="outline">
<SunIcon className="h-5 w-5 dark:hidden" />
<MoonIcon className="absolute h-5 w-5 hidden dark:block" />
<span className="sr-only">Toggle theme</span>
</ActionIcon>
</Dropdown.Trigger>
<Dropdown.Menu>
<Dropdown.Item onClick={() => setTheme('light')}>Light</Dropdown.Item>
<Dropdown.Item onClick={() => setTheme('dark')}>Dark</Dropdown.Item>
<Dropdown.Item onClick={() => setTheme('system')}>System</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
);
}

Step 5

Configure globals.css

Configure globals.css​

RizzUI 2.0 uses Tailwind CSS v4 with the modern OKLCH color space for better color perception. Create or update your app/globals.css:

/* Import Tailwind CSS v4 */
@import 'tailwindcss';

/* Configure content sources for RizzUI components */
@source '../node_modules/rizzui/dist';

/* Enable enhanced form styles */
@plugin '@tailwindcss/forms';

/* Enable dark mode support */
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));

/* Light theme colors */
:root {
/* Base Colors */
--background: oklch(100% 0 0); /* #ffffff */
--foreground: oklch(40.17% 0 0); /* #484848 */
--muted: oklch(91.58% 0 0); /* #e3e3e3 */
--muted-foreground: oklch(66% 0 0); /* #929292 */

/* Border Tokens */
--border-radius: 0.5rem; /* 8px */
--border-width: 0.0625rem; /* 1px */
--border-color: oklch(90.37% 0 0); /* #dfdfdf */

/* Text Tokens */
--text-primary: oklch(0% 0 0); /* #000000 */
--text-secondary: oklch(40.17% 0 0); /* #484848 */

/* Primary Brand Colors */
--primary-lighter: oklch(91.58% 0 0); /* #e3e3e3 */
--primary: oklch(17.76% 0 0); /* #111111 */
--primary-dark: oklch(0% 0 0); /* #000000 */
--primary-foreground: oklch(100% 0 0); /* #ffffff */

/* Secondary Colors */
--secondary-lighter: oklch(91.99% 0.0386 276.02); /* #dde3ff */
--secondary: oklch(50.51% 0.2633 276.95); /* #4e36f5 */
--secondary-dark: oklch(45.41% 0.2431 277.06); /* #432ad8 */
--secondary-foreground: oklch(100% 0 0); /* #ffffff */

/* Danger/Error Colors */
--red-lighter: oklch(89.99% 0.0393 14); /* #f7d4d6 */
--red: oklch(59.6% 0.2445 29.23); /* #ee0000 */
--red-dark: oklch(51.71% 0.2121 29.2338); /* #c50000 */

/* Warning Colors */
--orange-lighter: oklch(95.67% 0.0452 84.5695); /* #ffefcf */
--orange: oklch(78.37% 0.1587 72.99); /* #f5a623 */
--orange-dark: oklch(54.83% 0.1339 53.95); /* #ab570a */

/* Info Colors */
--blue-lighter: oklch(91.66% 0.0404 257.5078); /* #d3e5ff */
--blue: oklch(57.31% 0.2144 258.25); /* #0070f3 */
--blue-dark: oklch(51.58% 0.1888 258.27); /* #0761d1 */

/* Success Colors */
--green-lighter: oklch(92.79% 0.086 155.61); /* #b9f9cf */
--green: oklch(64.01% 0.1776 148.74); /* #11a849 */
--green-dark: oklch(53.79% 0.1441 149.52); /* #11843c */
}

/* Dark theme colors */
[data-theme='dark'] {
/* Base Colors */
--background: oklch(14.11% 0.0112 275.23); /* #08090e */
--foreground: oklch(90.37% 0 0); /* #dfdfdf */
--muted: oklch(32.11% 0 0); /* #333333 */
--muted-foreground: oklch(51.03% 0 0); /* #666666 */

/* Border Tokens */
--border-color: oklch(91.58% 0 0); /* #e3e3e3 */

/* Text Tokens */
--text-primary: oklch(100% 0 0); /* #ffffff */
--text-secondary: oklch(51.03% 0 0); /* #666666 */

/* Primary Brand Colors */
--primary-lighter: oklch(25.2% 0 0); /* #222222 */
--primary: oklch(95.81% 0 0); /* #f1f1f1 */
--primary-dark: oklch(100% 0 0); /* #ffffff */
--primary-foreground: oklch(0% 0 0); /* #000000 */

/* Secondary Colors */
--secondary-lighter: oklch(26.35% 0.1154 280.96); /* #1f165a */
--secondary-dark: oklch(85.2% 0.0733 276.238); /* #c1cbff */

/* Danger/Error Colors */
--red-lighter: oklch(27.08% 0.1111 29.23); /* #500000 */
--red-dark: oklch(86.69% 0.0714 18.6304); /* #ffc1c1 */

/* Warning Colors */
--orange-lighter: oklch(28.29% 0.0698 49.34); /* #441d04 */
--orange-dark: oklch(93.15% 0.1175 98.83); /* #fcea8b */

/* Info Colors */
--blue-lighter: oklch(32.05% 0.0873 254.4); /* #0d335e */
--blue-dark: oklch(90.53% 0.0611 225.72); /* #b5e9ff */

/* Success Colors */
--green-lighter: oklch(27.23% 0.0672 152.71); /* #033016 */
--green-dark: oklch(92.79% 0.086 155.61); /* #b9f9cf */
}

/* Map CSS variables to Tailwind's color system */
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);

--border-radius: var(--border-radius);
--color-border: var(--border-color);

--color-text-primary: var(--text-primary);
--color-text-secondary: var(--text-secondary);

--color-primary-lighter: var(--primary-lighter);
--color-primary: var(--primary);
--color-primary-dark: var(--primary-dark);
--color-primary-foreground: var(--primary-foreground);

--color-secondary-lighter: var(--secondary-lighter);
--color-secondary: var(--secondary);
--color-secondary-dark: var(--secondary-dark);
--color-secondary-foreground: var(--secondary-foreground);

--color-red-lighter: var(--red-lighter);
--color-red: var(--red);
--color-red-dark: var(--red-dark);

--color-orange-lighter: var(--orange-lighter);
--color-orange: var(--orange);
--color-orange-dark: var(--orange-dark);

--color-blue-lighter: var(--blue-lighter);
--color-blue: var(--blue);
--color-blue-dark: var(--blue-dark);

--color-green-lighter: var(--green-lighter);
--color-green: var(--green);
--color-green-dark: var(--green-dark);
}

Note: use our theming tools to customize these CSS variables

Step 6

Configure PostCSS

Create or update your postcss.config.mjs:

const config = {
plugins: {
'@tailwindcss/postcss': {},
},
};
export default config;

You are now ready to use dark mode in your application. Tailwind CSS v4 requires no tailwind.config.js file.

Test Your Dark Mode​

Use the theme switcher component to toggle between light and dark modes:

import { Button } from 'rizzui';
import { ThemeSwitcher } from '@/components/theme-switcher';

export default function App() {
return (
<div>
<ThemeSwitcher />
<Button>Get Started</Button>
</div>
);
}

Your application will now seamlessly switch between light and dark themes based on user preference.