@amberdaugherty/theme
v0.1.3
Published
Reusable theme system: follows system theme, per-tab manual override (new tab resets), Tailwind v4 + shadcn-style tokens.
Readme
@amberdaugherty/theme
可复用的 React 主题系统:跟随系统主题、当前 tab 可手动切换、新开 tab 自动重置回跟随系统。Tailwind v4 + shadcn 风格 token。
两种用法:
- 完整模式:自带
ThemeToggle组件 + shadcn 风格 token(配合 Tailwind v4)。 - Headless 模式:纯逻辑(
ThemeProvider+useTheme),UI、样式、定位完全自定义。
特性
- 🖥️ 跟随系统:默认读
prefers-color-scheme,系统切换时实时跟随 - 🔄 三态切换:系统 → 浅色 → 深色 循环
- 🗂️ per-tab 隔离:手动选择存在
sessionStorage,只影响当前 tab;新开 tab 自动重置回跟随系统,同 tab 刷新则保留 - 🧩 Headless 友好:核心只是切
<html>的.darkclass + 管记忆,不绑定任何样式方案 - 🎨 可选 shadcn 风格 token:语义化变量 + Tailwind v4 映射(完整模式才用)
- 🌫️ 无首屏闪烁:模块加载即应用一次
安装
npm install @amberdaugherty/themeReact ≥ 18。完整模式还需 tailwindcss + @tailwindcss/vite;Headless 模式无任何样式依赖。
用法 A:完整模式(ThemeToggle + token)
自带切换按钮和配色,开箱即用。
1. 主 CSS
/* src/index.css */
@import "tailwindcss";
@import "@amberdaugherty/theme/tokens.css";
/* ⚠ 必需:让 Tailwind 扫到包里 ThemeToggle 的 class(fixed/top-4 等),
否则这些工具类不会生成,定位/样式会失效。 */
@source "../node_modules/@amberdaugherty/theme";2. Vite 配置 Tailwind 插件
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
});3. 入口
import { ThemeProvider, ThemeToggle } from "@amberdaugherty/theme";
import "./index.css";
createRoot(document.getElementById("root")!).render(
<ThemeProvider>
<ThemeToggle />
<App />
</ThemeProvider>,
);组件里用语义化 Tailwind class,主题自动跟随:
<main className="bg-background text-foreground">
<p className="text-muted-foreground">跟随主题的文字</p>
</main>用法 B:Headless 模式(纯逻辑,自定义 UI)
只用 ThemeProvider + useTheme,不引 tokens.css、不用 ThemeToggle、不需要 @source。按钮、配色、定位全部自己写。
// src/main.tsx —— 只包 Provider,不引任何包样式
import { ThemeProvider } from "@amberdaugherty/theme";
createRoot(document.getElementById("root")!).render(
<ThemeProvider>
<App />
</ThemeProvider>,
);// 自己的切换按钮
import { useTheme } from "@amberdaugherty/theme";
export function MyToggle() {
const { theme, cycle } = useTheme();
return (
<button onClick={cycle}>
{theme === "dark" ? "🌙" : theme === "light" ? "☀️" : "🖥️"}
</button>
);
}/* 自己配色:包负责切 <html> 上的 .dark class,你用它写样式即可 */
:root { --my-bg: #fff; --my-fg: #111; }
.dark { --my-bg: #111; --my-fg: #fff; }
body { background: var(--my-bg); color: var(--my-fg); }Headless 模式下,包只做三件事:切
.darkclass、跟随系统、per-tab 记忆。其余一概由你决定(连 Tailwind 都不需要)。
什么时候需要 tokens.css / @source?
| 你用到的 | tokens.css | @source |
| --- | :---: | :---: |
| ThemeToggle(包自带的组件) | ✅ 需要 | ✅ 需要 |
| 只用 useTheme(Headless) | ❌ 不需要 | ❌ 不需要 |
@source 的作用:让消费方的 Tailwind 扫描包里的 class 字符串。只有用了包里带 Tailwind class 的组件(目前是 ThemeToggle)才需要。纯 hook 不涉及 class,自然不需要。
API
<ThemeProvider>
包裹在应用根部,无需 props。
useTheme()
const { theme, resolved, setTheme, cycle } = useTheme();| 字段 | 类型 | 说明 |
| --- | --- | --- |
| theme | "system" \| "light" \| "dark" | 用户选择 |
| resolved | "light" \| "dark" | 实际生效值(system 时取系统当前值) |
| setTheme(t) | (t: Theme) => void | 显式设置 |
| cycle() | () => void | 循环切换:系统 → 浅 → 深 |
<ThemeToggle />(完整模式)
三态切换按钮,默认固定右上角(fixed top-4 right-4)。接受可选 className 追加样式(如 className="left-4 right-auto" 移到左上)。
可用 token(完整模式)
tokens.css 定义并通过 @theme inline 暴露为 Tailwind 颜色:
background · foreground · card / card-foreground · muted / muted-foreground · border · input · primary / primary-foreground · accent / accent-foreground · ring
用法:bg-background、text-muted-foreground、border-border、bg-primary text-primary-foreground 等。圆角:rounded-lg / rounded-md / rounded-sm。改配色只动 tokens.css 里 :root 与 .dark 两套变量。
行为说明
- 选择存在
sessionStorage(per-tab),不写localStorage—— 这是「新开 tab 重置回跟随系统」的关键。 - 暗色通过给
<html>加.darkclass 切换。完整模式下配合 Tailwind v4 的@custom-variant dark,dark:变体也能用;Headless 模式下你直接用.dark选择器写自己的样式。
License
MIT
