next-forgenext-forge
Mask

Dark Mode

How to use dark mode in the design system.

next-forge comes with built-in dark mode support through the combination of Tailwind CSS and next-themes.

Implementation

The dark mode implementation uses Tailwind's darkMode: 'class' strategy, which toggles dark mode by adding a dark class to the html element. This approach provides better control over dark mode and prevents flash of incorrect theme.

The next-themes provider is already configured in the application, handling theme persistence and system preference detection automatically. Third-party components like Clerk's Provider and Sonner have also been preconfigured to respect this setup.

Usage

By default, each application theme will default to the user's operating system preference.

To allow the user to change theme manually, you can use the ModeToggle component which is located in the Design System package. We've already added it to the app sidebar and web navbar, but you can import it anywhere:

page.tsx
import { ModeToggle } from '@repo/design-system/components/mode-toggle';
 
const MyPage = () => (
  <ModeToggle />
);

You can check the theme by using the useTheme hook directly from next-themes. For example:

page.tsx
import { useTheme } from 'next-themes';
 
const MyPage = () => {
  const { resolvedTheme } = useTheme();
 
  return resolvedTheme === 'dark' ? 'Dark mode baby' : 'Light mode ftw';
}

Caveats

Currently, it's not possible to change the Clerk theme to match the exact theme of the app. This is because Clerk's Theme doesn't accept custom CSS variables. We'd like to be able to add the following in the future:

packages/design-system/providers/clerk.tsx
const variables: Theme['variables'] = {
  // ...
 
  colorBackground: 'hsl(var(--background))', 
  colorPrimary: 'hsl(var(--primary))', 
  colorDanger: 'hsl(var(--destructive))', 
  colorInputBackground: 'hsl(var(--transparent))', 
  colorInputText: 'hsl(var(--text-foreground))', 
  colorNeutral: 'hsl(var(--neutral))', 
  colorShimmer: 'hsl(var(--primary) / 10%)', 
  colorSuccess: 'hsl(var(--success))', 
  colorText: 'hsl(var(--text-foreground))', 
  colorTextOnPrimaryBackground: 'hsl(var(--text-foreground))', 
  colorTextSecondary: 'hsl(var(--text-muted-foreground))', 
  colorWarning: 'hsl(var(--warning))', 
};

On this page