在 Next.js 中使用 next-themes
的 ThemeProvider
是一个非常方便的方式来实现主题切换功能,例如支持浅色模式、深色模式或系统偏好主题。以下是详细的用法说明:
next-themes
首先,确保你已经在项目中安装了 next-themes
。可以通过以下命令安装:
npm install next-themes
# 或
yarn add next-themes
ThemeProvider
next-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
: 可能是为了解决开发中的闪烁问题,或者开发者更喜欢即时切换的效果。