Skip to Content
路漫漫其修远兮,吾将上下而求索

Next.js + Tailwind 主题切换原理详解

一、核心概念

主题切换的本质:通过修改 <html> 标签的 class,让 CSS 选择器自动匹配不同的样式


二、完整流程演示

步骤1:你在组件中写的代码

<div className="bg-white dark:bg-gray-800 text-black dark:text-white"> 这是一段文字 </div>

步骤2:Tailwind 编译后的 CSS

/* 日间模式样式(默认) */ .bg-white { background-color: white; } .text-black { color: black; } /* 夜间模式样式(需要父级有 .dark class) */ .dark .dark\:bg-gray-800 { background-color: #1f2937; } .dark .dark\:text-white { color: white; }

关键点: .dark .dark\:bg-gray-800 中间有空格,这是 CSS 的后代选择器

步骤3:实际的 HTML 结构

日间模式时:

<html class="light"> <body> <div class="bg-white dark:bg-gray-800 text-black dark:text-white"> 这是一段文字 </div> </body> </html>

CSS 匹配结果:

  • .bg-white 匹配 → 白色背景
  • .text-black 匹配 → 黑色文字
  • .dark .dark:bg-gray-800 不匹配(因为 html 是 light,不是 dark
  • .dark .dark:text-white 不匹配

最终效果:白底黑字

夜间模式时:

<html class="dark"> <body> <div class="bg-white dark:bg-gray-800 text-black dark:text-white"> 这是一段文字 </div> </body> </html>

CSS 匹配结果:

  • .bg-white 匹配 → 白色背景
  • .text-black 匹配 → 黑色文字
  • .dark .dark:bg-gray-800 匹配!→ 灰色背景(覆盖白色
  • .dark .dark:text-white 匹配!→ 白色文字(覆盖黑色

最终效果:灰底白字


三、CSS 后代选择器详解

什么是后代选择器?

.父级 .子级 { /* 样式 */ }

意思是:在 class 为 父级 的元素内部,找到 class 为 子级 的所有后代元素

实际例子

<div class="container"> <p class="text">我会变红</p> <div> <p class="text">我也会变红(孙子元素)</p> </div> </div> <p class="text">我不会变红(不在 container 内)</p>
.container .text { color: red; }

应用到主题切换

.dark .dark\:bg-gray-800 { background-color: #1f2937; }

翻译成人话:

  • 在 class 为 dark 的元素内部
  • 找到 class 为 dark:bg-gray-800 的所有后代元素
  • 给它们应用灰色背景

四、next-themes 做了什么?

1. 修改 <html> 的 class

// 用户点击切换按钮 setTheme("dark") // next-themes 执行: document.documentElement.className = "dark"

结果:

<html class="dark">

2. 保存到 localStorage

localStorage.setItem('theme', 'dark')

下次刷新页面时自动恢复主题

3. 防止闪烁

<head> 注入脚本,页面加载前就设置 class:

<script> // 页面加载前立即执行 const theme = localStorage.getItem('theme') if (theme === 'dark') { document.documentElement.classList.add('dark') } </script>

五、为什么所有组件都能自动切换?

不是 next-themes 给每个组件加效果,而是浏览器的 CSS 引擎自动匹配!

类比理解

就像你写:

button:hover { background: blue; }

浏览器会自动检测鼠标悬停,不需要你手动给每个 button 加效果。

同理,dark: 前缀也是浏览器自动检测 <html class="dark">,不需要手动操作每个组件。


六、完整切换流程图

用户点击切换按钮 setTheme("dark") next-themes 修改 <html class="dark"> 浏览器重新计算 CSS 所有 .dark .dark:xxx 选择器生效 页面上所有带 dark: 前缀的样式自动应用 主题切换完成!

七、实际代码示例

Tailwind 配置(已配置好)

// tailwind.config.js module.exports = { darkMode: 'class', // 使用 class 模式 }

组件中使用

// 任何组件都可以这样写 <div className="bg-white dark:bg-gray-900"> <h1 className="text-black dark:text-white">标题</h1> <p className="text-gray-600 dark:text-gray-300">内容</p> </div>

切换按钮

import { useTheme } from "next-themes" function ThemeToggle() { const { setTheme, resolvedTheme } = useTheme() return ( <button onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}> 切换主题 </button> ) }

八、常见问题

Q1: 为什么要在 <html> 上加 class,不能在 <body> 上吗?

A: 可以,但 <html> 是最顶层元素,确保所有内容都能被选择器覆盖。

Q2: 如果我不用 Tailwind,能用 next-themes 吗?

A: 可以!你可以手动写 CSS:

/* 日间模式 */ .my-card { background: white; color: black; } /* 夜间模式 */ .dark .my-card { background: #1f2937; color: white; }

Q3: 性能会不会有问题?

A: 不会。CSS 选择器匹配是浏览器原生能力,非常快。只是改一个 class,浏览器自动重新渲染。


九、总结

  1. next-themes 只做一件事:修改 <html class="dark">
  2. Tailwinddark: 前缀编译成 .dark .dark:xxx 后代选择器
  3. 浏览器 自动匹配 CSS 选择器,应用对应样式
  4. 不需要遍历组件,不需要手动加效果,全靠 CSS 原生能力

这就是为什么一个简单的 class 切换,能让整个网站的主题瞬间改变!

Last updated on