Shadcn-ui codebase analysis: Perfect Next.js dark mode in 2 lines of code with next-themes.
Tags: javascript, nextjs, opensource, shadcnui
Published: 2024-06-03 21:36:49

So I wanted to figure out how [ui.shadcn.com](http://ui.shadcn.com) implemented dark mode on their website. I looked at its [source code](https://github.com/shadcn-ui/ui/blob/main/apps/www/app/layout.tsx). These things usually lie in providers.

![](https://media.licdn.com/dms/image/D4E12AQHaNMwhN2LNjw/article-inline_image-shrink_1500_2232/0/1717449447046?e=1723075200&v=beta&t=tEzGDEwcnnlgbs9mTCymVaQBRtFg60_0EOal5pXyxi4)

That is when I came across [next-themes](https://github.com/pacocoursey/next-themes/tree/main). You can easily add dark mode to your next.js app using next-themes. In this article, you will learn the below concepts:

1.  How to configure next-themes in Next.js?
2.  How is next-themes configured in shadcn-ui/ui?
3.  useTheme to toggle dark/light modes.
4.  How is useTheme written in shadcn-ui/ui?

> _Want to learn how to build shadcn-ui/ui from scratch? Check out_ [_build-from-scratch_](https://github.com/Ramu-Narasinga/build-from-scratch) _and give it a star if you like it. Sovle challenges to build shadcn-ui/ui from scratch. If you are stuck or need help?_ [_there is a solution_](https://tthroo.com/build-from-scratch)_._

How to configure next-themes in Next.js?
----------------------------------------

### With app/

You’ll need to update your app/layout.jsx to use next-themes. The simplest layout looks like this:

```
// app/layout.jsx
export default function Layout({ children }) {
  return (
    <html>
      <head />
      <body>{children}</body>
    </html>
  )
}
```

Adding dark mode support takes 2 lines of code:

```
// app/layout.jsx
import { ThemeProvider } from 'next-themes'
export default function Layout({ children }) {
  return (
    <html suppressHydrationWarning>
      <head />
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  )
}
```

Note that ThemeProvider is a client component, not a server component.

> _Note! If you do not add_ [_suppressHydrationWarning_](https://reactjs.org/docs/dom-elements.html#suppresshydrationwarning:~:text=It%20only%20works%20one%20level%20deep) _to your_ <html> _you will get warnings because_ next-themes _updates that element. This property only applies one level deep, so it won't block hydration warnings on other elements._

How is next-themes configured in shadcn-ui/ui?
----------------------------------------------

The answer is in [Layout.tsx](https://github.com/shadcn-ui/ui/blob/main/apps/www/app/layout.tsx#L89), it has a [ThemeProvider](https://github.com/shadcn-ui/ui/blob/main/apps/www/components/providers.tsx#L10) imported from [@/components/providers.tsx](https://github.com/shadcn-ui/ui/blob/main/apps/www/components/providers.tsx#L10). Shadcn-ui/ui follows the same configuration provided in the official documentation, except for few properties. They are shown in the image below:

![](https://media.licdn.com/dms/image/D4E12AQG2sSFbALv2Vw/article-inline_image-shrink_1500_2232/0/1717449445888?e=1723075200&v=beta&t=zSJYHkdqDLZP16YyoVFCPWN3rxr6mSMIuz7G-A6Em1M)

*   attribute = 'data-theme': HTML attribute modified based on the active theme, accepts class and data-\* (meaning any data attribute, data-mode, data-color, etc.) ([example](https://github.com/pacocoursey/next-themes/tree/main#class-instead-of-data-attribute))
*   defaultTheme = 'system': Default theme name (for v0.0.12 and lower the default was light). If enableSystem is false, the default theme is light
*   enableSystem = true: Whether to switch between dark and light based on prefers-color-scheme
*   disableTransitionOnChange = false: Optionally disable all CSS transitions when switching themes ([example](https://github.com/pacocoursey/next-themes/tree/main#disable-transitions-on-theme-change))

useTheme to toggle dark/light modes.
------------------------------------

Your UI will need to know the current theme and be able to change it. The useTheme hook provides theme information:

```
import { useTheme } from 'next-themes'

const ThemeChanger = () => {
  const { theme, setTheme } = useTheme()

  return (
    <div>
      The current theme is: {theme}
      <button onClick={() => setTheme('light')}>Light Mode</button>
      <button onClick={() => setTheme('dark')}>Dark Mode</button>
    </div>
  )
}
```

> _Warning! The above code is hydration unsafe and will throw a hydration mismatch warning when rendering with SSG or SSR. This is because we cannot know the_ theme _on the server, so it will always be_ undefined _until mounted on the client._

How is useTheme written in shadcn-ui/ui?
----------------------------------------

[mode-toggle.tsx](https://github.com/shadcn-ui/ui/blob/13d9693808badd4b92811abac5e18dc1cddf2384/apps/www/components/mode-toggle.tsx#L6) exports a component named ModeToggle which is used in [site-header.tsx](https://github.com/shadcn-ui/ui/blob/13d9693808badd4b92811abac5e18dc1cddf2384/apps/www/components/site-header.tsx#L9)

ModeToggle in the header on [ui.shadcn.com](http://ui.shadcn.com) is shown below:

![](https://media.licdn.com/dms/image/D4E12AQH4BiVBGU5UqA/article-inline_image-shrink_1500_2232/0/1717449446075?e=1723075200&v=beta&t=S7Lwl_SscfBkprYkVQp7RSgc5xuZIV0d4b03N5_l2-I)

The following code is from mode-toggle.tsx

```
"use client"

import \* as React from "react"
import { MoonIcon, SunIcon } from "@radix-ui/react-icons"
import { useTheme } from "next-themes"

import { Button } from "@/registry/new-york/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/registry/new-york/ui/dropdown-menu"

export function ModeToggle() {
  const { setTheme } = useTheme()

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="ghost" className="w-9 px-0">
          <SunIcon className="h-\[1.2rem\] w-\[1.2rem\] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
          <MoonIcon className="absolute h-\[1.2rem\] w-\[1.2rem\] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
          <span className="sr-only">Toggle theme</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onClick={() => setTheme("light")}>
          Light
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("dark")}>
          Dark
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("system")}>
          System
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
```

In order to switch modes, setTheme function is exposed by the useTheme hook.

Conclusion:
-----------

This approach seems quite straight forward, except for few additional properties passed into ThemeProvider in shadcn/ui. You need to first configure the ThemeProvider and then use the useTheme hook to switch dark/light modes.

About me:
---------

Website: [https://ramunarasinga.com/](https://ramunarasinga.com/)

Linkedin: [https://www.linkedin.com/in/ramu-narasinga-189361128/](https://www.linkedin.com/in/ramu-narasinga-189361128/)

Github: [https://github.com/Ramu-Narasinga](https://github.com/Ramu-Narasinga)

Email: [ramu.narasinga@gmail.com](mailto:ramu.narasinga@gmail.com)

References:
-----------

1.  [https://github.com/shadcn-ui/ui/blob/main/apps/www/app/layout.tsx](https://github.com/shadcn-ui/ui/blob/main/apps/www/app/layout.tsx)
2.  [https://github.com/shadcn-ui/ui/blob/main/apps/www/components/providers.tsx#L10](https://github.com/shadcn-ui/ui/blob/main/apps/www/components/providers.tsx#L10)
3.  [https://github.com/pacocoursey/next-themes/tree/main](https://github.com/pacocoursey/next-themes/tree/main)
4.  [https://github.com/shadcn-ui/ui/blob/13d9693808badd4b92811abac5e18dc1cddf2384/apps/www/components/mode-toggle.tsx#L6](https://github.com/shadcn-ui/ui/blob/13d9693808badd4b92811abac5e18dc1cddf2384/apps/www/components/mode-toggle.tsx#L6)
5.  [https://github.com/shadcn-ui/ui/blob/13d9693808badd4b92811abac5e18dc1cddf2384/apps/www/components/site-header.tsx#L9](https://github.com/shadcn-ui/ui/blob/13d9693808badd4b92811abac5e18dc1cddf2384/apps/www/components/site-header.tsx#L9)
6.  [https://github.com/shadcn-ui/ui/blob/13d9693808badd4b92811abac5e18dc1cddf2384/apps/www/app/(app)/layout.tsx](https://github.com/shadcn-ui/ui/blob/13d9693808badd4b92811abac5e18dc1cddf2384/apps/www/app/(app)/layout.tsx)