接口主题开关
1)原则
1.系统>扫描。主题不仅仅是背景反转,而是一组令牌(颜色,背景,对比度,阴影,状态,插图,图形)。
2.System-first.默认值为System("prefers-color-scheme"),可手动选择Light/Dark/High-Contrast。
3.默认对比。目标是WCAG AA,用于小文本/重要标签-AAA。
4.没有爆发。将主题应用到渲染(inline脚本),然后轻轻过渡。
5.品牌稳定性。状态的重音和语义保留在所有主题中。
2)模式和脚本
Light-白天脚本/支付表格/长读。
黑暗是晚上/低光照度/轻量级比赛;减少眩光。
系统-我们遵循操作系统/浏览器("prefers-color-scheme")。
High-Contrast-增强对比度并最小化珠宝(亮片运动)。
Seasonal/Promo(可选)-在锦标赛/活动的基本主题之上(不打破令牌)。
3)令牌体系结构
我们保留语义令牌而不是直接颜色:css
:root {
/ semantics/
--bg: hsl(0 0% 99%);
--bg-elev: hsl(0 0% 100%);
--fg: hsl(220 15% 15%);
--muted: hsl(220 10% 45%);
--accent: hsl (260 95% 60%) ;/brand accent/
--success: hsl(152 55% 40%);
--warning: hsl(36 85% 45%);
--danger: hsl(0 75% 50%);
--border: hsl(220 10% 90%);
--focus: hsl(260 95% 60% /.6);
--shadow: 0 6px 24px hsl(220 20% 10% /.08);
/ typography/radii/
--radius: 12px;
--lh: 1. 4;
}
:root[data-theme="dark"]{
--bg: hsl(220 18% 10%);
--bg-elev: hsl(220 18% 14%);
--fg: hsl(0 0% 96%);
--muted: hsl(220 10% 70%);
--accent: hsl (260 95% 65%) ;/slightly lighter in the dark/
--border: hsl(220 14% 22%);
--shadow: 0 8px 28px hsl(220 50% 2% /.6);
}
:root[data-theme="hc"]{
/ high-contrast: simple pairs, high Lc/
--bg: #000; --bg-elev:#000; --fg:#fff; --muted:#fff;
--accent:#00E; --success:#0F0; --warning:#FF0; --danger:#F00;
--border:#fff; --focus:#0FF;
}
规则:组件仅使用语义令牌。
4)检测器和保存选择
html
<script>
(function(){
const saved = localStorage. getItem('theme'); // 'light' 'dark' 'hc' 'system' null const sys = window. matchMedia('(prefers-color-scheme: dark)'). matches? 'dark': 'light';
const theme = saved && saved!=='system'? saved: sys;
document. documentElement. setAttribute('data-theme', theme);
})();
</script>
UI开关:"Light/Dark/System/High-Contrast"。选择"System"时,不存储特定颜色,仅存储标志。收听操作系统更改:
js matchMedia('(prefers-color-scheme: dark)'). addEventListener('change', e=>{
if(localStorage. getItem('theme')==='system'){
document. documentElement. setAttribute('data-theme', e. matches? 'dark': 'light');
}
});
5)没有FOUC的平稳过渡
在下载CSS (inline脚本)之前应用主题。
主题动画是简短的,仅限于"颜色/背景/边界颜色"(120-200毫秒),但首次渲染时不是:css
@media (prefers-reduced-motion: no-preference){
html. theme-ready { transition: color. 18s, background-color. 18s, border-color. 18s; }
}
挂载应用程序后,添加"class="theme-ready"。
6)组件和状态
文本/图标:AA ≥对比;次要文本不低于4。5:1(在礼物中,很容易"褪色")。
字段/卡片:背景"--bg-elev",边界"--border",阴影"--shadow"。
CTA:背景"-accent",文本对比("#ff"或可计算)。
状态(hover/focus/active/disabled):改变亮度/阿尔法而不是"彩虹"。
图形/火花线:光线/黑暗的单独调色板;网格/轴是低对比度但可读的。
7)图像/媒体/标志
图标是单色的-通过"currentColor"(调整为文本)。
品牌徽标不得反转;准备两个版本(光/暗)。
海报/屏幕截图:易于阅读的文字(8-12%)。
SVG:避免"硬"填充,使用vars 'var (-fg)'/'var (--accent)'。
8)可用性
高对比:单独的"数据主题"预期="hc"。
焦环: 总是可见的('outline: 2px solid var (-focus);outline-offset: 2px`).
不要依赖颜色。状态图标/文本/模式。
字体:'font-variant-numeric: tabular-nums;'用于和数/分数。
RTL:主题不会打破镜像(使用逻辑属性)。
9)表演
颜色-根本上的CSS自定义属性→即时重新使用而无需组件重新渲染。
避免在大容器上使用"invert()"过滤器"重新粉刷"图像。
懒惰地替换主题的重插图(如果需要)。
不要在JS中存储大型调色板-主题由类/属性控制。
10) iGaming的细节
夜间现场直播:"软"对比(数字为AAA),系数变化的突出显示不引人注目,没有闪烁。
负责任的游戏:提醒和提示在两个主题中都是可读的;晚上没有"有毒"的花朵。
票房:字段/签名/错误,没有繁琐的发光口音;成功/错误在主题上是稳定的。
锦标赛"皮肤":仅作为表面上的accent-override,不要打破基本令牌。
11) Snippets UI
单选按钮(HTML/JS):html
<label class="theme-switch">
<span> Topic </span>
<select id="theme">
<option value = "system "> System </option>
<option value = "light "> Bright </option>
<option value = "dark "> Dark </option>
<option value = "hc"> High contrast </option>
</select>
</label>
<script>
const sel = document. getElementById('theme');
sel. value = localStorage. getItem('theme') 'system';
sel. addEventListener('change', e=>{
const v = e. target. value;
localStorage. setItem('theme', v);
if(v==='system'){
const sys = matchMedia('(prefers-color-scheme: dark)'). matches? 'dark':'light';
document. documentElement. setAttribute('data-theme', sys);
} else {
document. documentElement. setAttribute('data-theme', v);
}
});
</script>
组件预设:
css
.btn{height:44px; padding:0 16px; border-radius:var(--radius); display:inline-flex; align-items:center; gap:8px}
.btn--primary{background:var(--accent); color:#fff}
.card{background:var(--bg-elev); border:1px solid var(--border); box-shadow:var(--shadow); border-radius:var(--radius)}
.text-muted{color:var(--muted)}
React hook (persist + system):
tsx import { useEffect, useState } from 'react';
type Theme = 'light' 'dark' 'hc' 'system';
export function useTheme(){
const [theme, setTheme] = useState<Theme>(()=>localStorage. getItem('theme') as Theme 'system');
useEffect(()=>{
const apply = (t:Theme)=>{
const v = t==='system'? (matchMedia('(prefers-color-scheme: dark)'). matches? 'dark':'light'): t;
document. documentElement. setAttribute('data-theme', v);
};
apply(theme);
const mql = matchMedia('(prefers-color-scheme: dark)');
const on = ()=> theme==='system' && apply('system');
mql. addEventListener('change', on);
return ()=> mql. removeEventListener('change', on);
},[theme]);
useEffect(()=> localStorage. setItem('theme', theme),[theme]);
return { theme, setTheme };
}
12)度量标准
主题采用率:用户在Dark/System/HC上的份额。
FOUC比例:起始时可见"白色激增"的比例(<1%)。
Contrast defects:按发行版对比错误的数量。
Error visibility:由于不同主题中"不可见"的错误而导致的点击/重播。
Energy impact (mobile):比较会话时间(间接指标)。
13)反模式
反转所有"过滤器:invert (1)"-打破品牌和意义。
无需标记即可直接更改组件中的颜色。
将焦点环隐藏在黑暗的主题中。
深色背景上的文本太暗(或浅色背景上的文字)。
长过渡到整个页面(简化)。
一个主题中的状态的"独家"颜色不在另一个主题中。
14) QA支票清单
对比和可读性
- AA ≥的所有文本;AAA ≥的关键标签/小文本。
- 错误/成功/警告不仅可以用颜色区分。
行为
- 系统尊重"prefers-color-scheme"并响应操作系统更改。
- 没有FOUC(主题在渲染之前适用)。
- 切换主题不会重置页面状态。
组件
- 卡/表格/表格仅使用令牌。
- 图形具有两个主题的调色板。
- 徽标/图标在两个主题中都可以正确看到。
A11y
- 可见焦点;High-Contrast模式可用。
- 考虑到"prefers-reduced-motion"。
表演
- 过渡≤ 200毫秒;没有全局反射。
- 容器上没有重型过滤器/搅拌机。
15)设计系统中的文档
主题:调色板、对比度、状态(hover/focus/active/disabled)。
Guides:如何在不倒退对比的情况下添加新的白兰地口音。
图表/媒体:用于光/黑暗的预建调色板。
模式:系统第一,高对比,平稳切换,没有FOUC。
Do/Do n't:过滤器反转,线色、不可见错误/焦点。
简短摘要
主题的工作开关是语义令牌+系统第一+无害的开始。捕捉对比度,集中色彩,尊重"prefers-color-scheme"和reduce-motion,存储用户的选择,避免严重影响。然后,UI在任何环境中都保持可读性和可识别性-从夜间直播比赛到白天结帐和锦标赛屏幕。