1. Core concepts
  2. Dark mode

Core concepts

Dark mode

Using variants to style your site in dark mode.

Overview

Now that dark mode is a first-class feature of many operating systems, it's becoming more and more common to design a dark version of your website to go along with the default design.

To make this as easy as possible, Tailwind includes a dark variant that lets you style your site differently when dark mode is enabled:

Light mode

Writes upside-down

The Zero Gravity Pen can be used to write in any orientation, including upside-down. It even works in outer space.

Dark mode

Writes upside-down

The Zero Gravity Pen can be used to write in any orientation, including upside-down. It even works in outer space.

<div class="bg-white dark:bg-gray-800 rounded-lg px-6 py-8 ring shadow-xl ring-gray-900/5">  <div>    <span class="inline-flex items-center justify-center rounded-md bg-indigo-500 p-2 shadow-lg">      <svg class="h-6 w-6 stroke-white" ...>        <!-- ... -->      </svg>    </span>  </div>  <h3 class="text-gray-900 dark:text-white mt-5 text-base font-medium tracking-tight ">Writes upside-down</h3>  <p class="text-gray-500 dark:text-gray-400 mt-2 text-sm ">    The Zero Gravity Pen can be used to write in any orientation, including upside-down. It even works in outer space.  </p></div>

By default this uses the prefers-color-scheme CSS media feature, but you can also build sites that support toggling dark mode manually by overriding the dark variant.

Adding Dark/Light Color Theme

If you want colors to change automatically based on the light or dark mode, you can utilize color-scheme to define a light and dark theme and define it in your @theme

tailwind.css

@import 'tailwindcss';@import "./colors.css" layer(base);@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *, .dark, .dark *));@theme {  --color-background: var(--color-background);  --color-background-foreground: var(--color-foreground);  --color-foreground: var(--color-foreground);  --color-foreground-primary: var(--color-primary-foreground);  --color-primary: var(--color-primary);  --color-primary-foreground: var(--color-primary-foreground);  --color-secondary: var(--color-secondary);  --color-secondary-foreground: var(--color-secondary-foreground);  --color-destructive: var(--color-destructive);  --color-destructive-foreground: var(--color-destructive-foreground);  --color-muted: var(--color-muted);  --color-muted-foreground: var(--color-muted-foreground);  --color-accent: var(--color-accent);  --color-accent-foreground: var(--color-accent-foreground);  --color-popover: var(--color-popover);  --color-popover-foreground: var(--color-popover-foreground);  --color-card: var(--color-card);  --color-card-foreground: var(--color-card-foreground);  --color-border: var(--color-border);  --color-input: var(--color-input);  --color-ring: var(--color-ring);  --color-chart-1: var(--color-chart-1);  --color-chart-2: var(--color-chart-2);  --color-chart-3: var(--color-chart-3);  --color-chart-4: var(--color-chart-4);  --color-chart-5: var(--color-chart-5);  --color-sidebar: var(--color-sidebar-background);  --color-sidebar-foreground: var(--color-sidebar-foreground);  --color-sidebar-primary: var(--color-sidebar-primary);  --color-sidebar-primary-foreground: var(--color-sidebar-primary-foreground);  --color-sidebar-accent: var(--color-sidebar-accent);  --color-sidebar-accent-foreground: var(--color-sidebar-accent-foreground);  --color-sidebar-border: var(--color-sidebar-border);  --color-sidebar-ring: var(--color-sidebar-ring);}

colors.css

html {  color-scheme: light dark;  &[data-theme="light"] {    --color-background: var(--color-white);    --color-foreground: var(--color-neutral-950);    --color-card: var(--color-white);    --color-card-foreground: var(--color-neutral-950);      --color-popover: var(--color-white);    --color-popover-foreground: var(--color-neutral-950);      --color-primary: var(--color-neutral-900);    --color-primary-foreground: var(--color-neutral-50);      --color-secondary: var(--color-neutral-100);    --color-secondary-foreground: var(--color-neutral-900);      --color-muted: var(--color-neutral-100);    --color-muted-foreground: var(--color-neutral-500);      --color-accent: var(--color-neutral-100);    --color-accent-foreground: var(--color-neutral-900);      --color-destructive: var(--color-red-500);    --color-destructive-foreground: var(--color-neutral-50);      --color-border: var(--color-neutral-200);    --color-input: var(--color-neutral-200);    --color-ring: var(--color-neutral-900);      --color-chart-1: var(--color-neutral-500);    --color-chart-2: var(--color-neutral-600);    --color-chart-3: var(--color-neutral-700);    --color-chart-4: var(--color-neutral-800);    --color-chart-5: var(--color-neutral-900);      --color-sidebar-background: var(--color-neutral-100);    --color-sidebar-foreground: var(--color-neutral-800);    --color-sidebar-primary: var(--color-neutral-700);    --color-sidebar-primary-foreground: var(--color-neutral-50);    --color-sidebar-accent: var(--color-neutral-300);    --color-sidebar-accent-foreground: var(--color-neutral-900);    --color-sidebar-border: var(--color-neutral-200);    --color-sidebar-ring: var(--color-neutral-600);  }  &[data-theme="dark"] {    --color-background: var(--color-neutral-900);    --color-foreground: var(--color-neutral-50);      --color-card: var(--color-neutral-950);    --color-card-foreground: var(--color-neutral-50);      --color-popover: var(--color-neutral-900);    --color-popover-foreground: var(--color-neutral-50);      --color-primary: var(--color-neutral-50);    --color-primary-foreground: var(--color-neutral-900);      --color-secondary: var(--color-neutral-800);    --color-secondary-foreground: var(--color-neutral-50);      --color-muted: var(--color-neutral-800);    --color-muted-foreground: var(--color-neutral-400);      --color-accent: var(--color-neutral-800);    --color-accent-foreground: var(--color-neutral-50);      --color-destructive: var(--color-red-900);    --color-destructive-foreground: var(--color-neutral-50);      --color-border: var(--color-neutral-800);    --color-input: var(--color-neutral-800);    --color-ring: var(--color-neutral-300);      --color-chart-1: var(--color-neutral-600);    --color-chart-2: var(--color-neutral-700);    --color-chart-3: var(--color-neutral-800);    --color-chart-4: var(--color-neutral-900);    --color-chart-5: var(--color-neutral-950);      --color-sidebar-background: var(--color-neutral-900);    --color-sidebar-foreground: var(--color-neutral-100);    --color-sidebar-primary: var(--color-neutral-800);    --color-sidebar-primary-foreground: var(--color-neutral-50);    --color-sidebar-accent: var(--color-neutral-700);    --color-sidebar-accent-foreground: var(--color-neutral-100);    --color-sidebar-border: var(--color-neutral-800);    --color-sidebar-ring: var(--color-neutral-600);  }}

Toggling dark mode manually

If you want your dark theme to be driven by a CSS selector instead of the prefers-color-scheme media query, override the dark variant to use your custom selector:

app.css
@import "tailwindcss";@custom-variant dark (&:where(.dark, .dark *));

Now instead of dark:* utilities being applied based on prefers-color-scheme, they will be applied whenever the dark class is present earlier in the HTML tree:

HTML
<html class="dark">  <body>    <div class="bg-white dark:bg-black">      <!-- ... -->    </div>  </body></html>

How you add the dark class to the html element is up to you, but a common approach is to use a bit of JavaScript that updates the class attribute and syncs that preference to somewhere like localStorage.

Using a data attribute

To use a data attribute instead of a class to activate dark mode, just override the dark variant with an attribute selector instead:

app.css
@import "tailwindcss";@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));

Now dark mode utilities will be applied whenever the data-theme attribute is set to dark somewhere up the tree:

HTML
<html data-theme="dark">  <body>    <div class="bg-white dark:bg-black">      <!-- ... -->    </div>  </body></html>

With system theme support

To build three-way theme toggles that support light mode, dark mode, and your system theme, use a custom dark mode selector and the window.matchMedia() API to detect the system theme and update the html element when needed.

Here's a simple example of how you can support light mode, dark mode, as well as respecting the operating system preference:

spaghetti.js
// On page load or when changing themes, best to add inline in `head` to avoid FOUCdocument.documentElement.classList.toggle(  "dark",  localStorage.currentTheme === "dark" ||    (!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches),);// Whenever the user explicitly chooses light modelocalStorage.currentTheme = "light";// Whenever the user explicitly chooses dark modelocalStorage.currentTheme = "dark";// Whenever the user explicitly chooses to respect the OS preferencelocalStorage.removeItem("theme");

Again you can manage this however you like, even storing the preference server-side in a database and rendering the class on the server — it's totally up to you.

Copyright © 2025 Tailwind Labs Inc.·Trademark Policy