# Dark mode

How the theme switches, how to force light in a region, and how components follow.

## The contract

Dark mode is the `.dark` class on `<html>`. That class is the whole contract between the design
system and whatever manages the theme: next-themes, a script, a server-rendered cookie. Every
token has a dark value, so components need no `dark:` classes of their own for color.

## With next-themes

```tsx
import { ThemeProvider } from "next-themes"

<ThemeProvider
  attribute="class"
  defaultTheme="system"
  enableSystem
  disableTransitionOnChange
>
  <App />
</ThemeProvider>
```

`disableTransitionOnChange` stops every transition from animating during the switch.

## Without a provider

Set the class before the first paint, so the page never flashes the wrong theme:

```html
<script>
  const stored = localStorage.getItem("theme")
  const dark =
    stored === "dark" ||
    (stored !== "light" && matchMedia("(prefers-color-scheme: dark)").matches)
  document.documentElement.classList.toggle("dark", dark)
</script>
```

This site does exactly that.

## Forcing light in a region

Mark a subtree `.force-light` and it renders with the light palette even inside `.dark`. The
auth screens use it. The `dark:` variant is also disabled inside the region.

```tsx
<div className="force-light bg-background">...</div>
```

## Reading the theme in JavaScript

`useColorScheme()` returns `"light"` or `"dark"` from the class on `<html>` and updates when it
changes. The [Toaster](/components/sonner) uses it; use it when a third-party widget needs the
theme as a prop.

```tsx
import { useColorScheme } from "@zenoxai/ui/hooks/use-color-scheme"

const scheme = useColorScheme()
```

## The `dark:` variant

Prefer tokens. Reach for `dark:` only for an adjustment that is not a color role, such as a
different opacity on a decorative image.
