在 Next.js 中使用 next-themes 的 ThemeProvider 是一个非常方便的方式来实现主题切换功能,例如支持浅色模式、深色模式或系统偏好主题。以下是详细的用法说明:
next-themes首先,确保已经在项目中安装了 next-themes。可以通过以下命令安装:
npm install next-themes
# 或
yarn add next-themes
ThemeProvidernext-themes 提供了一个 ThemeProvider 组件,你需要将其包裹在应用的根组件中。通常是在 app/layout.tsx(Next.js 13+ App Router)或 pages/_app.tsx(Pages Router)中。
app/layout.tsx)// app/layout.tsx
import { ThemeProvider } from 'next-themes';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}
pages/_app.tsx)// pages/_app.tsx
import { ThemeProvider } from 'next-themes';
import type { AppProps } from 'next/app';
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<Component {...pageProps} />
</ThemeProvider>
);
}
attribute="class": 指定主题通过 HTML 的 class 属性应用(例如 <html class="dark">)。也可以设置为 data-theme(例如 <html data-theme="dark">)。defaultTheme="system": 默认主题,设置为 "system" 表示跟随系统偏好,也可以是 "light" 或 "dark"。enableSystem: 启用系统主题检测,允许根据用户设备的 prefers-color-scheme 自动切换主题。useTheme 钩子next-themes 提供了一个 useTheme 钩子,用于在组件中获取当前主题或动态切换主题。由于它需要运行在客户端,你需要在组件顶部添加 "use client"(App Router)。
// components/ThemeSwitcher.tsx
"use client";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
export default function ThemeSwitcher() {
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
// 确保组件在客户端渲染后才显示,避免 hydration 不一致
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
return (
<div>
当前主题: {theme}
<button onClick={() => setTheme("light")}>浅色模式</button>
<button onClick={() => setTheme("dark")}>深色模式</button>
<button onClick={() => setTheme("system")}>系统模式</button>
</div>
);
}
// app/page.tsx
import ThemeSwitcher from "@/components/ThemeSwitcher";
export default function Home() {
return (
<main>
<h1>欢迎使用 Next.js</h1>
<ThemeSwitcher />
</main>
);
}
如果你使用 Tailwind CSS 来实现主题样式,可以在 tailwind.config.js 中启用 darkMode: 'class',以便根据 class 属性切换样式。
// tailwind.config.js
module.exports = {
darkMode: "class", // 使用 class 切换主题
content: ["./app/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
background-color: white;
color: black;
}
.dark body {
background-color: black;
color: white;
}
你可以通过 themes 属性支持更多自定义主题:
<ThemeProvider themes={["light", "dark", "pink", "blue"]} attribute="data-theme">
{children}
</ThemeProvider>
然后在 CSS 中使用 [data-theme="pink"] 等选择器定义样式。
如果某页面需要强制使用特定主题,可以在页面组件上设置 forcedTheme:
// app/force-dark/page.tsx
export default function ForceDarkPage() {
return <h1>这是强制深色模式的页面</h1>;
}
ForceDarkPage.theme = "dark";
在 layout.tsx 中读取:
<ThemeProvider forcedTheme={process.browser ? ForceDarkPage.theme : null}>
{children}
</ThemeProvider>
suppressHydrationWarning 到 <html> 标签,避免服务端和客户端渲染不一致的警告。useTheme 只能在客户端运行,因此需要检查组件是否已挂载。next-themes 会确保无闪烁加载。通过以上步骤,你就可以在 Next.js 中使用 ThemeProvider 和 next-themes
<ThemeProvider
attribute='data-theme'
forcedTheme='light'
defaultTheme='light' // TODO: change to 'system' when dark mode ready
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
这是一个比较有特点的使用方式,接下来我们分析一下:
attribute='data-theme'
data-theme,而不是默认的 class。这意味着主题会以 data-theme 属性的形式应用到 <html> 标签上,例如 <html data-theme="light">。attribute="class",主题会以类名形式应用,例如 <html class="dark">。选择 data-theme 更适合某些 CSS 框架(例如 DaisyUI),或者当你希望避免类名冲突时。next-themes 如何标记当前主题,供 CSS 选择器使用。forcedTheme='light'
"light"),无论用户的偏好或代码中的动态切换逻辑如何。这会覆盖 defaultTheme 和 enableSystem 的效果。forcedTheme,允许用户通过 setTheme 或系统偏好动态切换主题。你这里的设置表明当前项目可能只支持浅色模式,或者开发者希望暂时锁定主题。defaultTheme='light'
"light",即在没有用户偏好或系统设置的情况下使用的主题。注释中提到未来计划改为 "system",表明开发者可能在准备深色模式。defaultTheme="system",默认跟随系统主题(例如用户设备设置为深色模式时自动切换)。你这里固定为 "light",可能因为深色模式尚未实现。enableSystem
next-themes 根据用户的设备设置(prefers-color-scheme)自动切换主题。enableSystem,这点相同。但在你的代码中,由于 forcedTheme="light" 的存在,enableSystem 的效果被覆盖,除非移除 forcedTheme。disableTransitionOnChange
next-themes 会为主题切换添加一个平滑的过渡效果(例如颜色渐变),设置此参数后,切换会立即生效。| 参数 | 你的代码 | 我的代码 | 主要差异 |
|---|---|---|---|
attribute |
'data-theme' |
'class' |
你使用 data-theme 属性,我使用 class,影响 CSS 选择器写法 |
forcedTheme |
'light' |
未设置 | 你强制使用浅色主题,我允许动态切换 |
defaultTheme |
'light' |
'system' |
你默认浅色,我默认跟随系统 |
enableSystem |
true |
true |
相同,但你因 forcedTheme 无效 |
disableTransitionOnChange |
true |
未设置(默认 false) |
你禁用过渡动画,我保留默认动画效果 |
attribute='data-theme': 如果你使用像 DaisyUI 或其他支持 data-theme 的 CSS 框架,这个设置很常见。CSS 示例:[data-theme="light"] {
background-color: white;
color: black;
}
[data-theme="dark"] {
background-color: black;
color: white;
}
forcedTheme='light': 表明当前项目可能还在开发中,深色模式未就绪。通过强制浅色主题,避免用户看到未完成的深色样式。defaultTheme='light': 与 forcedTheme 搭配,确保即使移除 forcedTheme,初始状态也是浅色。enableSystem: 为将来支持系统主题做准备,一旦移除 forcedTheme,就能自动适配用户设备的深色/浅色偏好。disableTransitionOnChange: 可能是为了解决开发中的闪烁问题,或者开发者更喜欢即时切换的效果。