深色模式
在 HeroUI v3 中添加浅色、深色以及跟随系统的主题切换
HeroUI 的深色模式由 CSS 驱动。组件会从根元素读取主题变量,因此你不需要使用 HeroUI Provider。只需在 <html> 上添加 dark 类或 data-theme="dark",HeroUI 就会应用深色主题。
<html class="dark" data-theme="dark">
<body class="bg-background text-foreground">
<!-- Your app -->
</body>
</html>请保持你的应用根元素上有 bg-background 与 text-foreground 类,这样页面画布才会随着当前主题变化。
HeroUI 内置的浅色和深色主题同时响应 .light / .dark 类以及 data-theme="light" / data-theme="dark" 属性。如果你手动同时设置两者,请确保它们的值保持一致。
在 Next.js 中使用 next-themes
当你在 Next.js 应用中需要主题持久化、系统偏好支持,并希望在水合(hydration)前不出现闪烁时,请使用 next-themes。
安装 next-themes
npm i next-themespnpm add next-themesyarn add next-themesbun add next-themesApp Router
为 next-themes 创建一个客户端 Provider。
// app/providers.tsx
"use client";
import {ThemeProvider as NextThemesProvider} from "next-themes";
export function Providers({children}: {children: React.ReactNode}) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</NextThemesProvider>
);
}在根布局中用该 Provider 包裹你的应用。请在 <html> 上添加 suppressHydrationWarning,因为 next-themes 会在水合之前更新该元素。
// app/layout.tsx
import "./globals.css";
import {Providers} from "./providers";
export default function RootLayout({children}: {children: React.ReactNode}) {
return (
<html lang="en" suppressHydrationWarning>
<body className="bg-background text-foreground">
<Providers>{children}</Providers>
</body>
</html>
);
}主题切换器
在客户端组件中使用 next-themes 提供的 useTheme。请等到组件挂载后再渲染,因为在 SSR 期间无法得知当前激活的主题。
// app/components/theme-switcher.tsx
"use client";
import {Button} from "@heroui/react";
import {useTheme} from "next-themes";
import {useEffect, useState} from "react";
export function ThemeSwitcher() {
const [mounted, setMounted] = useState(false);
const {resolvedTheme, setTheme, theme} = useTheme();
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
const activeTheme = theme === "system" ? resolvedTheme : theme;
return (
<div className="flex items-center gap-2">
<Button
variant={activeTheme === "light" ? "primary" : "secondary"}
onPress={() => setTheme("light")}
>
Light
</Button>
<Button
variant={activeTheme === "dark" ? "primary" : "secondary"}
onPress={() => setTheme("dark")}
>
Dark
</Button>
<Button variant={theme === "system" ? "primary" : "secondary"} onPress={() => setTheme("system")}>
System
</Button>
</div>
);
}Pages Router
如果使用 pages/,请在 pages/_app.tsx 中包裹你的应用。
// pages/_app.tsx
import "@/styles/globals.css";
import type {AppProps} from "next/app";
import {ThemeProvider as NextThemesProvider} from "next-themes";
export default function App({Component, pageProps}: AppProps) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<Component {...pageProps} />
</NextThemesProvider>
);
}使用自定义主题名称
attribute="class" 这套配置非常适合内置的 light 和 dark 主题。如果你的自定义主题 CSS 是基于 data-theme 选择器编写的,请改为让 next-themes 写入 data-theme。
<NextThemesProvider
attribute="data-theme"
defaultTheme="system"
enableSystem
themes={["light", "dark", "ocean", "ocean-dark"]}
>
{children}
</NextThemesProvider>当你传入自定义的 themes 列表时,如果仍然希望保留内置主题,请将 "light" 和 "dark" 一并包含进去。
在 React 中使用 useTheme
当你正在构建一个普通的 React 应用(例如 Vite 或 Create React App),并且不需要 next-themes 时,可以使用 HeroUI 提供的 useTheme 钩子。
该钩子从 @heroui/react 中导出。它会将当前选择的主题保存到 localStorage,根据用户的操作系统偏好解析 "system",并同时把对应的类与 data-theme 属性应用到 <html> 上。
// src/components/theme-switcher.tsx
import {Button, useTheme} from "@heroui/react";
export function ThemeSwitcher() {
const {resolvedTheme, setTheme, theme} = useTheme("system");
return (
<div className="flex items-center gap-2">
<Button
variant={resolvedTheme === "light" ? "primary" : "secondary"}
onPress={() => setTheme("light")}
>
Light
</Button>
<Button
variant={resolvedTheme === "dark" ? "primary" : "secondary"}
onPress={() => setTheme("dark")}
>
Dark
</Button>
<Button variant={theme === "system" ? "primary" : "secondary"} onPress={() => setTheme("system")}>
System
</Button>
</div>
);
}每个应用只应使用一个主题控制器。在 Next.js 中,推荐使用 next-themes 及其 useTheme 钩子;在普通的 React 应用中,请使用 @heroui/react 提供的 useTheme。
同时为两种主题设置样式
主题相关的工具类会自动生效,因为它们读取的是 CSS 变量:
<main className="min-h-screen bg-background text-foreground">
<section className="bg-surface text-surface-foreground shadow-surface">
Theme-aware content
</section>
</main>对于仅在深色模式下生效的一次性样式调整,请使用 dark: 变体:
<div className="bg-background text-foreground dark:border-default">
Custom dark-mode adjustment
</div>