主题做得乱,是因为颜色写死在组件里。换深色时要改 40 个 #fff,漏一个就是一块闪瞎眼的白卡片。把会变的值收成 token,组件只认名字。
先定三层
css
:root {
--bg: #f3f5fb;
--fg: #1b2233;
--accent: #3557c8;
--border: color-mix(in srgb, var(--fg) 12%, transparent);
}
.dark {
--bg: #14161f;
--fg: #e9ecf6;
--accent: #9bb0ff;
}
body {
background: var(--bg);
color: var(--fg);
}
a {
color: var(--accent);
}组件里不要再写 #3557C8。链接、按钮描边、图标都吃 --accent。你以后换一套蓝,改一处就够。
深色类打在 html 上
html
<html class="dark">不要打在 body 上。color-scheme 和滚动条、表单控件会跟着根元素走:
css
:root { color-scheme: light; }
.dark { color-scheme: dark; }这样原生 <select>、滚动条在深色里不会突然变成刺眼白块。
多套风格用额外 class
css
.style-minimalist {
--bg: #fcfcfc;
--fg: #1e293b;
--accent: #475569;
}
.dark.style-minimalist {
--bg: #0f172a;
--fg: #f1f5f9;
--accent: #cbd5e1;
}注意:浅色强调色和深色强调色几乎一定不是同一个值。浅底要用更深的强调色,深底要用更亮的。一套 #64748B 两边用,深色模式下链接会糊进背景。
组件只消费 token
css
.card {
background: var(--bg);
border: 1px solid var(--border);
color: var(--fg);
}
.card a { color: var(--accent); }本地存储里记 theme=dark 和 style=life,进入页面时往 <html> 上加 class。第一帧之前就加上,能少一次闪白。脚本放 <head> 即可,不必等 React 水合。
Token 是主题的合同。合同清楚,后面换皮肤才不是翻工。
