# a11y Contrast — 设计师决策清单（library 段）

> **✅ 2026-06-06 owner/设计师决策（Group A 收口）**：**Group A（白字 on 品牌/状态填充）= ACCEPT，设计师已确认当前已发布品牌色为 final**。设计师在定色时已参考 WCAG（方法论 + 探索见 Figma [`Brand Color Palette and variants`](https://www.figma.com/design/YbsPRUVmNdsbN40NNwh1Gn/TVU-UX-Design-System?node-id=1497-26855) node `1497:26855`），其中一套 WCAG-AA 合规替代（light `#118536` 白字=4.74 / dark `#30D158`+深字 `#043818`=6.56）被探索但**最终未采用**；**已发布并采用的是当前这套**（`--brand` dark `#2fb54e` / light `#299f45`，白字 2.68/3.42）。plan owner 已 deterministic 核实：live Figma 已发布 `UX/Brand/Brand` = `#2fb54e` 与 code `--brand` 一致（**code 未 stale，审计测的是真·已发布色**）。结论：白字 on 品牌/状态填充低于严格 AA 是**设计师知情的品牌决策**（品牌识别优先），登记为 accept，非 code bug、非待修。状态色（red/orange/blue，同"白字 on 饱和填充"族）随此决策一并 accept；如设计师后续要单独收紧某状态色再 reopen。**Group B**（灰 placeholder/disabled）= WCAG 1.4.3 豁免，accept。**Group C/D**（语义色文字 on 同色浅底 / 残余）= 低优先，按需。
>
> **✅ 2026-06-10 library 段全收口（owner ruling + plan owner 实测）**：**Group C = ACCEPT**——owner 拍板"以 Figma 为真源，不修改"，4 个语义文字 token（`--prompt-*-text` 等）保持现值，知情决策同 Group A 范式；D 内并入 C 的 #21/#43/#46 随之一并收口。**Group D #11/#8/#14 = 测量伪影实锤**——plan owner playwright 实测 light 主题：steps title（#11）/ datetime value（#8）/ select value（#14）的真实绘制底全是白色（`.canonical-page` / `.select-trigger` #ffffff，contrast ≥17:1 PASS），报告的 `#141414`/`#353535`/`#4a4a4a` 背景今日不可复现；根因 = **axe 背景检测漏识别中间白层、回退到 body 暗底**（body 在 light 主题下仍是 `#141414`，`.canonical-page` 白底盖其上）。**Group B #5 pagination = 同款伪影证伪**——`.pg-total`/`.pg-jumper__label` `#595959` 真实底 = `figma-members-grid__cell` #ffffff → **7.0:1 PASS**（非报告的 2.63 on #141414）→ Group B 唯一标 (a) 的项不成立，**B 全组无需改 Figma**（其余 ⚠️ 项按 owner "Figma 真源不动" 姿态默认 accept，要单独收紧再 reopen）。**结论：library 段 47 配对全部收口（A accept / B accept+伪影 / C accept / D 伪影），零 Figma token 改动需求。** 复跑 inspector 前注意 body-bg fallback 伪影类——同配对模式（light 主题 + `#141414` 背景）一律先疑伪影再交设计师。剩 **non-text 20 对**在另册 review，不在本清单范围。
>
> **✅ 2026-06-11 non-text 20 对收口（owner 拍板）**：全部 **accept = 有意设计**——弱边框/分隔线是这套设计语言的有意风格；select/input 等组件自带填充提供辨识冗余（2026-06-10 playwright 实测 select-trigger 在 #f0f0f0 卡片内有白色填充），边框非辨识组件的唯一视觉线索；且 20 对是 token×bg-layer 全枚举矩阵，非每对都在真实 UI 相邻出现。`--line-border`=`--bg-layer3` light 同值（#f0f0f0）owner 确认**有意设计**。**至此 contrast 设计师线（library 文字 47 对 + non-text 20 对）全部收口，零 Figma 改动**；CI a11y job 维持非阻塞（axe 数字未变，accept 型收口）。
>
> **生成**：2026-06-01（plan owner 把 [`a11y-contrast-report.md`](./a11y-contrast-report.md) 的 47 个 library 失败配对按**根因 token** 归并，给设计师一次过完）
> **真源关系**：本文件是 [`a11y-contrast-report.md`](./a11y-contrast-report.md) `library` 段的**决策视图**，不替代它——逐节点 selector / 样本仍查原报告对应 `### N` 编号。
> **为什么只有 library 段**：另两段 `docs-chrome`(29 配对) / `demo-local`(69 配对) 是 docs site 自身样式，开发处理，不走 Figma、不需要设计师。

---

## 怎么用这份文件

[`FIGMA_AS_SOURCE_OF_TRUTH.md`](../FIGMA_AS_SOURCE_OF_TRUTH.md)：**颜色真源在 Figma**，code 不能擅自改色。所以每个失败配对要由设计师做一个三选一决策：

- **(a) 改 Figma token 值** — 调暗/调亮到 ≥ WCAG AA，再 `pnpm sync:figma-library` 同步回 code
- **(b) 标 component-internal** — 不是公共 token，组件级局部覆盖即可
- **(c) accept（带理由）** — 仅当对比度是**有意**且属 WCAG 豁免场景（disabled / placeholder / 纯装饰非文本）

> 原报告把 (c) 标为 "discouraged per 1a"。本清单细化：**(c) 只在 WCAG 标准本身豁免的场景才用**（见 Group B 说明），不是随意放行。

**WCAG AA 门槛**：正常文字 4.5:1 · 大字（≥18px 或 ≥14px bold）/ UI 组件 3:1。

---

## TL;DR — 47 配对其实只是 ~9 个 token 决策

| Group | 配对数 | 节点数 | 根因 | 推荐默认动作 | 谁拍板 |
|---|---:|---:|---|---|---|
| **A. 白字 on 品牌/状态填充** | 10 | 48 | `--brand`/`--red`/`--orange`/`--blue`(+hover) 对白字太浅 | **设计师品牌决策**：调暗填充色 vs 归大字/UI 3:1 | 设计师 / 品牌 owner |
| **B. 灰色 placeholder/disabled 文字** | 15 | 93 | `--color-grey-5/6/7/8/9`(`--text-placeholder`/`--text-disabled`/`--text-tips`) | **多数 (c) accept** — WCAG 豁免；仅可读灰（pagination total）需 (a) | 设计师确认豁免 |
| **C. 语义色文字 on 同色调浅底** | ~13 | ~50 | `--brand`/`--blue`/`--orange`/`--red` 文字 on `--prompt-*-bg`/`--brand-match` | **(a) 调暗语义文字 token**（保留浅底 tint） | 设计师 |
| **D. 残余（疑似伪影 / 装饰）** | ~6 | ~17 | 见下逐条 | **case-by-case**（多为测量伪影或装饰分隔符） | plan owner 先核实再交设计师 |

**关键洞察**：Group B 的 93 个节点（占全部 45%）大多是 **WCAG 1.4.3 明确豁免**的 disabled/placeholder 文字——不构成真清理负担。真正需要设计师做色值决策的，收敛到 **~4 个品牌/状态填充色（A）+ ~4 个语义文字 token（C）+ 1 个可读灰（B 里的 `--text-tips`）≈ 9 个 token 决策**。

---

## Group A — 白字 on 品牌/状态填充（48 节点 / 10 配对）

白字压在品牌绿 / 红 / 橙 / 蓝实底上，对比度 2.19–3.94，**全部低于 4.5**，其中 6 个连大字/UI 的 3:1 都不到。

| 报告# | 配对 | 主题 | 比值 | 节点 | 组件 | 根因填充 token |
|---|---|---|---:|---:|---|---|
| #2 | `#ffffff` on `#2fb54e` | dark | 2.67 | 17 | button | `--brand` |
| #6 | `#ffffff` on `#299f45` | light | 3.42 | 12 | notification | `--brand` |
| #12 | `#ffffff` on `#ea4233` | dark | 3.94 | 6 | notification/badge | `--red` |
| #18 | `#ffffff` on `#2eb34d` | light | 2.73 | 4 | button | `--brand`(hover?) |
| #30 | `#ffffff` on `#3892f3` | dark | 3.19 | 2 | badge | `--blue` |
| #31 | `#ffffff` on `#f68512` | dark | 2.53 | 2 | badge | `--orange` |
| #32 | `#ffffff` on `#e26601` | light | 3.42 | 2 | badge | `--orange`(hover) |
| #33 | `#ffffff` on `#41c760` | dark | 2.19 | 1 | button | `--brand-hover` |
| #34 | `#ffffff` on `#3ac45c` | light | 2.27 | 1 | button | `--brand-hover` |
| #47 | `#ffffff` on `#2fb54e` | light | 2.67 | 1 | tabs | `--brand` |

**这是最有战略意义、也最难的一组**：白字绿按钮（#2，17 节点）是核心品牌按钮。

**决策（设计师 / 品牌 owner）**：
- **(a) 调暗品牌 / 状态填充色** 到 ≥ 目标比值——但调暗 `--brand` 会**改变品牌识别色**，量级不小（绿底要明显变深才够 4.5）。需品牌 owner 拍板，且要同时考虑 hover 变体（#33/#34）。
- 或 **接受为大字/UI 组件按 3:1 衡量**——但注意 #2(2.67)/#18(2.73)/#31(2.53)/#33(2.19)/#34(2.27)/#47(2.67) **连 3:1 都不到**，只有 #12/#30/#32/#6 在 3:1 与 4.5 之间，所以这条路只能救一部分。
- 推荐：先定 **`--brand` 一个色值**（覆盖 #2/#6/#18/#47 + hover #33/#34 大头），`--red`/`--orange`/`--blue` 状态色按需跟进。

- [ ] `--brand` → ________   (建议方向：调暗；目标 4.5 / 或 3:1+大字声明)
- [ ] `--brand-hover` → ________
- [ ] `--red` / `--orange` / `--blue` 填充 → ________ / 接受 3:1
- [ ] 或整组标大字/UI 3:1（声明哪些实例字号 ≥14px bold）

---

## Group B — 灰色 placeholder / disabled 文字（93 节点 / 15 配对）

`--color-grey-5/6/7/8/9` 系列，映射 `--text-placeholder` / `--text-disabled` / `--text-tips` / `--icon-disabled`。比值 1.15–4.35。

> **核心：WCAG 2.1 SC 1.4.3 豁免** ——"inactive/disabled UI 组件的文字"无对比度要求；placeholder 文字业界普遍按豁免处理（前提是它不承载必读信息）。所以这组**大部分应 (c) accept（理由：WCAG 豁免 disabled/placeholder）**，不是真 drift。

| 报告# | 配对 | 比值 | 节点 | 组件 | 根因 | 是否豁免 |
|---|---|---:|---:|---|---|---|
| #1 | `#7b7b7b` on `#353535` (dark) | 2.89 | 19 | select | `--color-grey-7` placeholder | ✅ placeholder |
| #3 | `#7b7b7b` on `#262626` (dark) | 3.57 | 15 | select | `--color-grey-7` placeholder | ✅ |
| #4 | `#9e9e9e` on `#f0f0f0` (light) | 2.35 | 15 | select | `--color-grey-6` placeholder | ✅ |
| #9 | `#9e9e9e` on `#f8f8f8` (light) | 2.52 | 8 | tabs | `--color-grey-6` | ⚠️ 见下 |
| #10/#13 | `#9e9e9e` on `#4c4c4c`/`#4a4a4a` | 3.20/3.30 | 11 | form/select | `--color-grey-6` placeholder | ✅ |
| #17 | `#7b7b7b` on `#141414` (dark) | 4.35 | 5 | steps | `--color-grey-7` | ⚠️ 已过 3:1，steps pending 标题 |
| #35/#36 | `#595959` on `#353535`/`#141414` | 1.75/2.63 | 2 | select | `--color-grey-8` disabled | ✅ disabled |
| #37/#38 | `#cccccc` on `#dbdbdb`/`#ffffff` | 1.15/1.60 | 2 | select | `--color-grey-5` disabled | ✅ disabled |
| #39/#40 | `#595959` on `#262626`/`#1f1f1f` | 2.16/2.35 | 2 | breadcrumb | `--color-grey-8` | ⚠️ breadcrumb 当前页文字 |
| #22 | `#434343` on `#1f1f1f` (light) | 1.66 | 2 | slider | `--color-grey-9` | ⚠️ 见下 |
| **#5** | **`#595959` on `#141414` (light)** | **2.63** | **12** | **pagination** | **`--text-tips`（total/jumper 文字）** | **❌ 真可读文字 → 需 (a)** |

**决策**：
- **大头（#1/#3/#4/#10/#13/#35/#36/#37/#38 ≈ 70 节点）**：placeholder + disabled → **(c) accept，理由 WCAG 豁免**（或如果设计师希望 placeholder 也达标，则 (a) 略调 `--text-placeholder`）。
- **#5 pagination total/jumper（12 节点）= 真正要读的正文灰**（不是 placeholder）→ **(a) 调暗 `--text-tips`**。这是 B 组里唯一明确该修的。
- **⚠️ 待设计师判定**：#9 tabs normal（非激活 tab 文字，可点 = 应可读？）/ #39/#40 breadcrumb / #22 slider / #17 steps pending-title——这些是否"有意弱化的次要文字"还是"该可读"，设计师定。

- [ ] `--text-tips` (pagination 正文) → ________  **(建议 (a) 调暗)**
- [ ] placeholder/disabled 整体 → (c) accept WCAG 豁免 / 或 (a) 调 `--text-placeholder`
- [ ] tabs normal / breadcrumb / slider / steps pending → 逐项 (a)/(b)/(c)

---

## Group C — 语义色文字 on 同色调浅底（~50 节点 / dark+light 各一份）

成功/信息/警告/错误**语义色文字**压在**同色调浅 tint 底**上（消息条 / 下拉激活项 / steps 激活 / form 错误提示）。比值 2.30–4.24。这是标准 a11y 取舍——**保留浅底、把文字调暗**即可。

| 报告#（dark/light） | 配对（hue） | 比值 | 节点 | 组件 | 根因文字 token |
|---|---|---:|---:|---|---|
| #19 / #20 | `#3892f3` on `#e1eefd`（蓝/info） | 2.71 | 8 | prompt-message | `--blue` / `--prompt-info-text` |
| #25 / #28 | `#f68512` on `#fff3db`（橙/warning） | 2.30 | 4 | prompt-message | `--orange` / `--prompt-warning-text` |
| #26 / #29 | `#2fb54e` on `#f1fbf3`（绿/success） | 2.52 | 4 | prompt-message | `--brand` / `--prompt-success-text` |
| #24 / #27 | `#ea4233` on `#fff1f0`（红/error） | 3.58 | 4 | prompt-message | `--red` / `--prompt-error-text` |
| #7 | `#299f45` on `#f1fbf3`（绿） | 3.23 | 8 | select 下拉激活项 | `--brand` on `--brand-match` |
| #23 | `#299f45` on `#f0f0f0`（绿） | 3.00 | 2 | steps 激活 | `--brand` |
| #16 | `#dc2717` on `#f0f0f0`（红） | 4.24 | 5 | form-item 错误 | `--red` |
| #15 | `#ea4233` on `#262626`（红/dark surface） | 3.83 | 5 | form-item 错误 | `--red` / `--prompt-error-text` |

**决策（设计师）**：**(a) 调暗 4 个语义文字 token**（`--prompt-info-text`/`--prompt-warning-text`/`--prompt-success-text`/`--prompt-error-text`，或其底层 `--blue`/`--orange`/`--brand`/`--red`），底色 tint 不动。注意 dropdown 激活项 #7 与 steps #23 也复用 `--brand` 文字——与 Group A 的 `--brand` 填充是**两个不同用途**（A 是填充底色，C 是文字色），调整时分开。

- [ ] `--prompt-info-text` (蓝) → ________
- [ ] `--prompt-warning-text` (橙) → ________
- [ ] `--prompt-success-text` / 下拉/steps 绿文字 → ________
- [ ] `--prompt-error-text` / form 错误红 → ________

---

## Group D — 残余（~17 节点）：先核实再交设计师

| 报告# | 配对 | 比值 | 节点 | 组件 | 判断 |
|---|---|---:|---:|---|---|
| #11 | `#000000` on `#141414` (light) | **1.13** | 6 | steps 激活标题 | ⚠️ **疑似测量伪影**：近黑压近黑（heading 文字 hex 落在 body 文字元素上）→ plan owner 先核实是否真叠层 bug |
| #8 | `#141414` on `#353535` (light) | 1.50 | 8 | datetime select-value | ⚠️ 选中值文字 on 深填充——核实该填充是否 disabled 态 |
| #14 | `#141414` on `#4a4a4a` (light) | 2.07 | 5 | select value | ⚠️ 同上 |
| #41/#42/#44/#45 | `#bdbdbd`/`#dedede` on `#f0f0f0`/`#f8f8f8` | 1.18–1.76 | 4 | breadcrumb | 装饰分隔符（chevron）→ 非文本 3:1 或装饰豁免 |
| #21/#43/#46 | `#2eb34d`/`#2fb54e` on `#f8f8f8`/`#f0f0f0` | 2.40–2.57 | 4 | button/breadcrumb/tabs | 实为 Group C 同类（绿文字 on 浅底，light 主题），并入 C 处理 |

**plan owner 待办**：#11 / #8 / #14 先用 render-harness 或 Figma 核实是真叠层 drift 还是 inspector 测量伪影（[[name-search-absent-fallacy]] / 启发式存疑），再决定交不交设计师。

---

## 给设计师的最小决策清单（≈9 项）

1. `--brand` 绿填充（白字按钮/notification/tabs）—— 调暗 or 大字 3:1？（Group A 大头）
2. `--brand-hover` 绿 —— 跟随 #1
3. `--red` / `--orange` / `--blue` 状态填充（白字 badge/notification）—— 调暗 or 3:1？
4. `--text-tips`（pagination 正文灰）—— 调暗（Group B 唯一真修）
5. placeholder/disabled 灰 —— 确认 (c) WCAG 豁免（Group B 大头）
6. `--prompt-info-text` 蓝文字 —— 调暗（Group C）
7. `--prompt-warning-text` 橙文字 —— 调暗（Group C）
8. `--prompt-success-text` / 下拉激活绿文字 —— 调暗（Group C）
9. `--prompt-error-text` / form 错误红 —— 调暗（Group C）

完成后：设计师在 Figma 改这些 token → `pnpm sync:figma-library --with-extract` → 复跑 `pnpm exec node scripts/a11y-contrast-inspector.mjs` 复核。
