Chapter 04

深色模式

dark: 前缀、class vs media 策略,以及带持久化的深色模式切换按钮完整实现

1. dark: 前缀基础

Tailwind 通过 dark: 前缀为深色模式提供专属样式。当深色模式激活时,dark: 前缀后的工具类生效,覆盖默认样式。

<!-- 浅色:白色背景 + 深色文字;深色:深色背景 + 浅色文字 -->
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
  自动适应深浅色主题
</div>

<!-- 完整的卡片深色模式 -->
<div class="
  bg-white dark:bg-gray-800
  border border-gray-200 dark:border-gray-700
  text-gray-900 dark:text-gray-100
  shadow-sm dark:shadow-gray-900/50
  rounded-xl p-6
">
  <h3 class="text-lg font-semibold mb-2 text-gray-900 dark:text-white">卡片标题</h3>
  <p class="text-gray-600 dark:text-gray-400 text-sm">
    卡片描述文字,在深浅色模式下都有良好可读性
  </p>
</div>

2. 深色模式策略:media vs class

media 策略(跟随系统)

默认策略。dark: 前缀基于 CSS 媒体查询 @media (prefers-color-scheme: dark),自动跟随用户操作系统的深浅色设置。

/* CSS 等价实现(Tailwind 自动生成)*/
@media (prefers-color-scheme: dark) {
  .dark\:bg-gray-900 {
    background-color: var(--color-gray-900);
  }
}

class 策略(手动切换)

v4 中通过 @variant dark 自定义深色模式策略。将策略改为 class,则当 <html>(或任意祖先元素)有 dark class 时,dark: 前缀生效。

/* src/style.css —— 改为 class 策略 */
@import "tailwindcss";

@variant dark (&:where(.dark, .dark *));
/* 等价于:祖先元素有 .dark 类时,dark: 前缀生效 */
<!-- HTML:手动在 <html> 上切换 dark 类 -->
<html class="dark">  <!-- 深色模式 -->
<html>              <!-- 浅色模式 -->
ℹ️

media vs class 选哪个?
如果只需跟随系统偏好,用默认的 media 策略即可。如果需要用户手动切换(网站上有深/浅色切换按钮),必须用 class 策略——因为 media 策略无法通过 JavaScript 控制。

3. 深色模式切换按钮完整实现

以下是一个带持久化(localStorage)的深色模式切换功能,是最常见的实现方式:

<!-- HTML 结构 -->
<button
  id="theme-toggle"
  class="
    p-2 rounded-lg
    bg-gray-100 dark:bg-gray-800
    text-gray-500 dark:text-gray-400
    hover:bg-gray-200 dark:hover:bg-gray-700
    transition-colors duration-200
  "
  aria-label="切换深色模式"
>
  <!-- 太阳图标(深色模式时显示)-->
  <svg id="icon-sun" class="hidden dark:block w-5 h-5" ...></svg>
  <!-- 月亮图标(浅色模式时显示)-->
  <svg id="icon-moon" class="block dark:hidden w-5 h-5" ...></svg>
</button>
// JavaScript:深色模式切换逻辑
const html = document.documentElement;
const btn = document.getElementById('theme-toggle');

// 初始化:读取用户偏好或系统设置
function initTheme() {
  const saved = localStorage.getItem('theme');
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

  if (saved === 'dark' || (!saved && prefersDark)) {
    html.classList.add('dark');
  } else {
    html.classList.remove('dark');
  }
}

// 切换
btn.addEventListener('click', () => {
  const isDark = html.classList.toggle('dark');
  localStorage.setItem('theme', isDark ? 'dark' : 'light');
});

// 跟随系统变化
window.matchMedia('(prefers-color-scheme: dark)')
  .addEventListener('change', (e) => {
    if (!localStorage.getItem('theme')) {
      html.classList.toggle('dark', e.matches);
    }
  });

initTheme(); // 页面加载时执行
⚠️

避免主题闪烁(FOUC):将 initTheme() 的调用放在 <head> 中的内联 <script> 里(不是 defer),确保 DOM 渲染前就设置好 class,避免浅色→深色的闪烁。

4. CSS 变量 + Tailwind 主题配合

v4 中,深色模式与 CSS 变量结合能创建更灵活的主题系统:

/* 定义亮色 / 暗色主题变量 */
@import "tailwindcss";

@variant dark (&:where(.dark, .dark *));

@theme {
  --color-background: white;
  --color-surface: #f9fafb;
  --color-text-primary: #111827;
  --color-text-secondary: #6b7280;
  --color-border: #e5e7eb;
}

/* 深色模式覆盖 CSS 变量 */
.dark {
  --color-background: #0f172a;
  --color-surface: #1e293b;
  --color-text-primary: #f1f5f9;
  --color-text-secondary: #94a3b8;
  --color-border: #334155;
}
<!-- 使用主题变量工具类(v4 自动生成 bg-background 等类)-->
<div class="bg-background text-text-primary border border-border">
  <!-- 自动适应深浅色,无需写 dark: 前缀 -->
  主题感知组件
</div>

本章小结:小项目用默认 media 策略跟随系统即可;需要手动切换时改用 class 策略,并配合 localStorage 持久化。CSS 变量 + @theme 的组合是构建完整主题系统的最佳实践,让深浅色切换只需改变 CSS 变量值,无需在每个元素上写 dark: