OpsMind UI 重构实录(二):设计 Token、暗色重构和双主题切换
OpsMind UI 重构实录(二):设计 Token、暗色重构和双主题切换
wwxdsg如果说第一天是在把链路打通,那第二天才真正进入了我最喜欢的部分:
开始给界面建立秩序。
因为我很清楚,这次我想要的并不是“页面功能更全了”,而是让整个 OpsMind 前端从一种“能用”的状态,慢慢变成一种我自己也愿意多看几眼的东西。
而这件事靠的不是多写几个渐变,也不是多加几个阴影。真正有用的,是先把所有零散样式背后的规则抽出来。
我想解决的,不是某一个颜色,而是混乱
很多界面到后面越来越难改,不是因为它们功能复杂,而是因为样式决策是散着长出来的。
这里一个 #27272a,那里一个 rgba(255,255,255,0.08),另一个组件又自己写了套 hover 色。短期看都能跑,长期看就会有一种典型后果:
你已经能感觉到风格不统一,但又不知道该先动哪一层。
所以这一天最核心的决定其实很简单:
把视觉决策先从组件里拿出来,收回到 token 层。
于是我开始搭一整套 CSS 自定义属性,把它当成单一数据源:
--bg |
这件事做完之后,组件终于不再负责“我今天到底是什么颜色”,它们只负责消费语义。
我很喜欢这种分工,因为它会让样式系统一下子变得很工程化:
- 组件写的是意图
- token 决定的是实现
- 主题切换只需要覆盖变量
这时候界面才真正开始“有骨架”。
暗色模式我几乎是重做了一遍
原来的暗色不是不能用,但总有一种还没完全收住的感觉。
颜色里残留着一些偏蓝紫的调子,短时间看挺显眼,长时间看就会有点累。
这次我索性全换到 Zinc 体系:
--bg: #09090b--surface: #18181b--surface-high: #27272a--text: #a1a1aa--text-bright: #e4e4e7
这里我最在意的一笔其实是正文色。
我没有让 AI 正文直接上特别亮的文字,而是把 --text 压在 zinc-400 这一档。原因很简单:聊天产品不是海报,正文不是拿来“亮”的,而是拿来读的。
标题、激活菜单、用户气泡这些地方可以更亮,但正文如果整块太刺,读久了会累得很快。
所以第二天我越来越确定一件事:
真正的质感,很多时候不是“更强烈”,而是“更克制”。
语义 token 才是双主题真正的关键
这次改主题时,我最不想继续忍的一个问题,就是那些写死的 rgba(...)。
因为它们在单一主题下经常“刚好能看”,一旦切到另一套主题,立刻就开始露馅。
最典型的就是这种值:
rgba(255,255,255,0.11) |
放在暗色里没问题,但丢到浅色里就很容易接近不可见。
如果组件里全是这种硬编码,最后主题切换基本就是灾难片。
所以我把这些交互色全部语义化了,比如:
--session-active-bg |
然后在暗色和浅色下各给一套对应值。
这一层做完之后,很多原来很麻烦的问题会突然简单很多。
比如会话项激活态、新建对话按钮、输入框阴影、卡片背景这些,全都不需要在组件里写 if else 了。
组件只写:
background: var(--session-active-bg); |
至于暗色是浅白透明,浅色是浅黑透明,交给 token 层处理。
浅色模式不是暗色模式的反相
第二天另一个让我很满意的点,是我终于认真把浅色模式当成一套独立系统来做,而不是暗色的附属品。
比如背景我没有用纯白,而是刻意留在 #f8f8f8。
因为纯白在很多屏幕上太刺,也太像“默认没设计”。
文字层级也不是简单反色,而是重新分级:
--text-bright负责标题和激活态--text负责正文和普通菜单--text-muted负责次级信息--text-subtle才是 placeholder 这种极淡内容
这里有个我觉得非常值得记住的小结论:
浅色模式里,text-muted 不能太淡。
如果偷懒直接用很浅的灰,白底上的对比度会掉得很厉害。看起来“高级”的同时,也会开始不好读。最后那种高级感通常维持不到三秒。
所以这次我宁愿它稳一点,也不追求那种一眼很轻、三眼就费劲的配色。
主题切换的实现本身,也得有点体面
有了 token 之后,主题切换本身就比较直接了:
:root { ...dark values... } |
再配一个 useTheme():
document.documentElement.classList.toggle('light', t === 'light') |
这一层其实不复杂,但我很在意另一个细节:别闪。
如果页面先按暗色渲染,再在 hydration 之后切成浅色,那一瞬间的闪烁会很破坏体验。
所以我在 app/layout.tsx 里加了一小段内联脚本,优先从 localStorage 里把主题 class 打上去。
这种代码看起来不浪漫,但它非常像真正产品里会在意的事。
用户未必会夸你“这个 FOUC 处理得真棒”,但他们会很自然地觉得页面很稳。
很多顺滑感,往往就是这么来的。
这一天最让我开心的,是界面终于开始“统一说话”
第二天做完之后,最大的变化不一定是某个组件一下子变好看了,而是整个界面开始有一种统一的语言:
- 同一类层级有同一类背景
- 同一类文本有同一类对比度
- hover、active、card、input 都在同一个语义系统里
- 暗色和浅色不再互相将就
这会让一个产品从“零件堆起来了”变成“像同一个人做的”。
而我自己最喜欢的,也正是这种时刻。
你会觉得自己不是在修一个个组件,而是在慢慢把一个前端界面的性格雕出来。
现在回头看,第二天决定了这次重构的上限
第一天解决的是功能和链路,当然重要。
但如果没有第二天这套 token 和双主题体系,后面很多细节优化其实都只能算局部补丁。
因为一旦底层语义不统一:
- 侧边栏怎么改都可能东一块西一块
- 输入框怎么磨都可能只是局部精致
- 新加一个组件又会重新发明自己的颜色系统
而把 token 层立住之后,整个前端才终于有了一个可以持续扩展的基础。
所以如果我要给第二天下一个定义,我会说:
这一天不是在加样式,而是在给整个 UI 建立秩序。
而秩序一旦建立起来,质感就不再完全靠灵感了。