Skip to content

Theme Toggle

Install this component.

Installation

npx shadcn@latest add https://ui.monark.io/r/theme-toggle.json

Source

"use client"

import * as React from "react"
import { MoonIcon, SunIcon } from "lucide-react"
import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes"

import { Button } from "@/components/ui/button"
import { cn } from "cn"

/**
 * next-themes provider with the Monark defaults: the `.dark` class on
 * `<html>`, following the system preference until the visitor picks one.
 * Wrap your root layout's `<body>` content in it (and add
 * `suppressHydrationWarning` to `<html>`), or use your own provider.
 */
function ThemeProvider({
  children,
  ...props
}: React.ComponentProps<typeof NextThemesProvider>) {
  return (
    <NextThemesProvider
      attribute="class"
      defaultTheme="system"
      enableSystem
      disableTransitionOnChange
      {...props}
    >
      {children}
    </NextThemesProvider>
  )
}

export interface ThemeToggleProps
  extends Omit<React.ComponentProps<typeof Button>, "children" | "onClick"> {
  /** Accessible name (and tooltip). Default "Toggle theme". */
  label?: string
}

/**
 * 36px ghost icon button that flips between light and dark. The moon shows
 * in light mode and the sun in dark mode; the swap is pure CSS (`dark:`),
 * so the server-rendered icon is already right and nothing flashes on
 * hydration. Needs a next-themes provider with `attribute="class"`.
 */
function ThemeToggle({ label = "Toggle theme", className, ...props }: ThemeToggleProps) {
  const { resolvedTheme, setTheme } = useTheme()
  return (
    <Button
      type="button"
      variant="ghost"
      size="icon"
      aria-label={label}
      title={label}
      data-slot="theme-toggle"
      className={cn("size-9 shrink-0 rounded-full [&_svg]:size-[18px]", className)}
      onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
      {...props}
    >
      <SunIcon className="hidden dark:block" aria-hidden="true" />
      <MoonIcon className="dark:hidden" aria-hidden="true" />
    </Button>
  )
}

export { ThemeToggle, ThemeProvider }