6.4 颜色 Token
学习目标
完成本节后,你将能够:
- 读懂 Langfuse 前端颜色不是散落的 Tailwind class,而是一套 CSS 变量契约。
- 区分基础 UI token、业务强调色、图表色、Search Bar 语法色。
- 判断新增 UI 或图表时应该复用哪个 token。
6.4.1 先给结论:Langfuse 的颜色服务于“高密度观测 UI”
Langfuse 不是营销页,也不是内容站。它的主界面是 tracing、scores、sessions、datasets、evals、dashboards 这类高密度工作台。因此颜色策略偏克制:
- 大面积背景保持低干扰;
- border、muted、card、popover 负责层级;
- primary accent 用于关键动作和当前状态;
- chart tokens 用于多序列分析;
- Search Bar 语法色帮助读 query;
- light/dark 通过同一组语义 token 切换。
源码入口是:
web/src/styles/globals.cssweb/src/features/score-analytics/lib/color-scales.tsweb/src/components/editor/shared-theme.ts
6.4.2 Token 的两层结构
第一层是 CSS 变量,例如:
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary-accent: 243 75.4% 58.6%;
--chart-1: 239 84% 58%;
}第二层是 Tailwind v4 的 @theme inline 映射:
@theme inline {
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-primary-accent: hsl(var(--primary-accent));
--color-chart-1: hsl(var(--chart-1));
}组件使用的是 bg-background、text-foreground、text-primary-accent 这类语义 token,而不是直接写十六进制颜色。这样 light/dark 和主题调整可以集中发生。
6.4.3 基础 UI 色板
| Token | 作用 | 典型场景 |
|---|---|---|
background / foreground | 页面底色和主要文字 | 页面主体、普通文本 |
card / card-foreground | 可复用内容块 | panel、dialog、列表项 |
popover / popover-foreground | 浮层 | dropdown、command palette、tooltip |
muted / muted-foreground | 次级背景和说明文字 | 空状态、辅助说明、低权重 metadata |
border / input / ring | 交互边界 | 表单、focus、分隔线 |
primary / primary-foreground | 主要按钮语义 | primary action |
destructive / destructive-foreground | 破坏性操作 | delete、danger state |
这套 token 的设计目标是让复杂页面先靠层级和间距可读,而不是靠大量彩色标签制造噪声。
6.4.4 业务强调色
globals.css 还定义了几组业务强调色:
| Token | 颜色意图 | 用法 |
|---|---|---|
primary-accent | 品牌/关键强调 | 当前导航、CTA、重要 active state |
muted-blue / muted-green / muted-magenta | 柔和彩色区分 | 轻量标签、状态辅助 |
light-red / dark-red | 错误、危险、失败 | error badge、失败状态 |
light-yellow / dark-yellow | warning、pending | warning badge、等待状态 |
light-green / dark-green | 成功、通过 | success badge、positive state |
light-blue / dark-blue | 信息、链接、辅助强调 | info badge、secondary highlight |
注意这些 token 通常成对出现:浅色负责背景,深色负责文字或边框。这样在高密度表格里不会出现大片高饱和色块。
6.4.5 Dark mode 不是反色
dark mode 在 .dark 里重新定义同一批语义变量:
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 83%;
--muted: 217.2 32.6% 17.5%;
--border: 217.2 32.6% 27.5%;
}这说明组件不应该自己判断 dark mode 后改颜色。组件应该继续使用语义 token,让主题层负责 light/dark。
错误示例:
<div className="bg-white dark:bg-slate-950 text-black dark:text-white" />更符合本项目的写法:
<div className="bg-background text-foreground" />6.4.6 图表颜色是单独契约
图表颜色由 --chart-1 到 --chart-8 管理:
| Token | 意图 |
|---|---|
chart-1 | 主序列 |
chart-2 | 第二序列 |
chart-3 | 中性色/单分数默认 |
chart-4 到 chart-8 | 多类别扩展 |
chart-grid | 图表网格线 |
web/src/features/score-analytics/lib/color-scales.ts 在这些 token 上继续封装:
- 单分数图使用
getSingleScoreColor(); - 双分数对比使用
getTwoScoreColors(); - heatmap 使用 OKLCH lightness 生成单色阶;
- fallback hex 保证 CSS variable 解析失败时仍能显示。
这说明图表颜色不是随便从 Tailwind palette 里取。它需要稳定映射,避免用户隐藏/显示分类时颜色漂移。
6.4.7 Search Bar 语法色
Search Bar 有自己的 query language token:
| Token | 表示 |
|---|---|
qlang-field | 字段名 |
qlang-value | 字符串值 |
qlang-number | 数字 |
qlang-keyword | 关键字 |
源码注释明确说这是一套“editor-style syntax theme”,它不完全跟随应用色板。原因是搜索栏类似代码编辑器,颜色职责是帮助用户解析语法结构,而不是表达业务状态。
这和架构契约也有关:Search Bar 不是随意拼查询字符串,它最终要生成 FilterState,再由后端 lower 成 ClickHouse SQL。
6.4.8 颜色如何服务架构
| UI 场景 | 颜色职责 | 对应架构 |
|---|---|---|
| trace / observation 表格 | 减少噪声,突出状态、层级和 hover | 高密度 observability 工作台 |
| Search Bar | 强化 query grammar | FilterState 契约 |
| score analytics | 保持序列和类别颜色稳定 | 分析查询和可视化 |
| destructive actions | 让危险操作和普通 action 分离 | 控制面变更 |
| dark mode | 通过 token 层统一切换 | 组件不感知主题实现 |
所以颜色不是纯视觉细节。对这种 infra/product 混合系统来说,颜色 token 是前端稳定性的组成部分。
6.4.9 新增颜色的原则
新增颜色前先问:
- 这是业务状态,还是普通视觉层级?
- 是否已有
muted、accent、destructive、chart-*能表达? - 这个颜色是否需要 light/dark 两套值?
- 是否会被图表、Search Bar、editor、table 共同使用?
- 是否需要稳定映射,避免数据重排后颜色变化?
如果只是一个局部装饰,不要加全局 token。全局 token 应该表达跨组件语义。