agentcss 0.11.7 → 0.11.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/CONTRIBUTING.md +9 -2
- package/README.zh-CN.md +8 -8
- package/ai/START-HERE.md +2 -2
- package/ai/components.json +1 -1
- package/docs/ACCESSIBILITY.md +76 -0
- package/docs/CONVENTIONS.md +194 -0
- package/docs/INVARIANT.md +2 -2
- package/index.html +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,46 @@
|
|
|
16
16
|
|
|
17
17
|
(无)
|
|
18
18
|
|
|
19
|
+
## [0.11.8] - 2026-10-29
|
|
20
|
+
|
|
21
|
+
**补上文档站最大的真空**:令牌用法、焦点环、动效时长、暗色切换、逻辑属性这些
|
|
22
|
+
「不属于任何组件」的规则,此前**没有任何页面承载**。症状不是少一页,是搜
|
|
23
|
+
「减少动效」「逻辑属性」「强制暗」全站 0 命中 —— 用户问的是意思,而站里没有
|
|
24
|
+
承载那个意思的地方。
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **全局约定页**(`docs/CONVENTIONS.md` → `guide/conventions.html`)。十节:
|
|
29
|
+
令牌三层(raw / 语义 / 组件私有,附实测对比度)、间距与圆角与层级的台阶、
|
|
30
|
+
焦点环(2px + 2px、为什么只用 `:focus-visible`、深底内白环)、动效三档时长
|
|
31
|
+
与"减少动效"的三条策略(位移关 / 真实进度留 / 颜色可缩短)、暗色三态与两个
|
|
32
|
+
选择器坑、逻辑属性与绝对定位写法、为什么**不用** `@layer`、`!important`
|
|
33
|
+
必须可分类、对比度与 44px 触控线、覆盖策略(改令牌而不是改组件),
|
|
34
|
+
末尾一张「每条规则由哪道判据挡住」的对应表。
|
|
35
|
+
- **无障碍页**(`docs/ACCESSIBILITY.md` → `guide/accessibility.html`):
|
|
36
|
+
目标等级、**验过的范围与手段**(含反向控制与实算对比度)、
|
|
37
|
+
**六条已知缺口**(无真机读屏记录、RTL 只有静态检查、强制色板只覆盖一部分、
|
|
38
|
+
Android 系统 WebView 未验、无 VPAT、9 处「待人工」未结论)、自测命令。
|
|
39
|
+
- **概念页这一页型**:生成器此前只认组件目录(`ai/components.json` 的 35 个),
|
|
40
|
+
跨组件的规则没有地方放。现在 `CONCEPT_PAGES` 是一等公民:进索引(排在最前)、
|
|
41
|
+
进搜索键表、有自己的目录与上下篇。
|
|
42
|
+
- **`md-links` 门禁 + 反向控制**:文档里的路径必须落地。
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **9 处文档指着不存在的文件**。`README.zh-CN.md` / `docs/INVARIANT.md` /
|
|
47
|
+
`ai/START-HERE.md` / `CONTRIBUTING.md` 一共 9 处指向 `docs/GUIDANCE.md`、
|
|
48
|
+
`docs/BENCHMARK.md`、`docs/ROADMAP.md`、`docs/RELEASING.md` —— 那四份在 0.11.4
|
|
49
|
+
被**故意**移进内部目录(含账号二次因子、到期日这类不能出门的内容),
|
|
50
|
+
公开面于是留下一排悬空指针。现在全部改指向真实存在的文件
|
|
51
|
+
(`docs/CONVENTIONS.md`、`CONTRIBUTING.md`、`API.md`、`CHANGELOG.md`)。
|
|
52
|
+
- 为什么一直没人发现:文档里的路径有**两种写法** —— markdown 链接与行内代码,
|
|
53
|
+
而既有判据都在看别的东西(pack-closure 判包内 HTML 引用、docs-site 判生成后的
|
|
54
|
+
页面链接)。`md-links.py` 两种都判,并对 CHANGELOG 显式豁免(那是历史,
|
|
55
|
+
改它等于篡改版本记录)。
|
|
56
|
+
- 搜索探针从 14 条加到 17 条:新增的三条(减少动效 / 逻辑属性 / 强制暗)
|
|
57
|
+
在加页面之前**确实是红的**,先红后绿才算判据有效。
|
|
58
|
+
|
|
19
59
|
## [0.11.7] - 2026-10-29
|
|
20
60
|
|
|
21
61
|
**交付物零变化,改的是"我们怎么验自己"**:外网泄漏审查此前**一个文件都没读到**
|
package/CONTRIBUTING.md
CHANGED
|
@@ -8,7 +8,14 @@ npm run gate # 全量:含浏览器类
|
|
|
8
8
|
npm run gate:fast # 只跑静态层,不用装浏览器
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
全量是 **
|
|
11
|
+
全量是 **183 道门禁**(本机约 12 分钟)。
|
|
12
|
+
|
|
13
|
+
两道环境依赖要自己装,否则对应的门禁会**跳过**(跳过不算通过):
|
|
14
|
+
|
|
15
|
+
| 依赖 | 少了会怎样 | 装法 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Node + Chromium | `unit` / `responsive` / `firefox` 与全部行为脚本跳过 | 按 `package.json` 的 `engines` 装 Node,浏览器由脚本自己拉起 |
|
|
18
|
+
| Pillow | `visual` 与 `visual-dark` 跳过 ⇒ **视觉回归一次都不跑** | `pip install pillow`(或用 `PY_PIL=<解释器>` 指一个装了 Pillow 的) |
|
|
12
19
|
|
|
13
20
|
⚠️ 上面的数字写在**代码块外**是有意的:`doc-facts-gate` 会核对
|
|
14
21
|
「文档里的门禁数」与 `check-all.sh` 的真实注册数,而**代码块内的内容被豁免**
|
|
@@ -79,7 +86,7 @@ fix: <一句话说清改了什么、为什么>
|
|
|
79
86
|
- **JS 必须过 ES5 门禁**:不用 `const` `let`、箭头函数、模板串、`class`、`async`、
|
|
80
87
|
`?.`、`??`。范围是 `01-tokens` ~ `09-assets`(`es5-gate.py` 守着)。
|
|
81
88
|
- **状态写在 DOM 属性上**,不写在 class 里(INVARIANT I-8)。
|
|
82
|
-
- **CSS 用 `@layer` 吗?** 现在不用,理由与切换判据见 `docs/
|
|
89
|
+
- **CSS 用 `@layer` 吗?** 现在不用,理由与切换判据见 `docs/CONVENTIONS.md` 的「不用 `@layer`」一节。
|
|
83
90
|
- 注释写**为什么**,不写是什么。取舍理由、踩过的坑、放弃的方案 ——
|
|
84
91
|
这些半年后有用;`.foo { color: red }` 没有。
|
|
85
92
|
|
package/README.zh-CN.md
CHANGED
|
@@ -69,8 +69,8 @@ npm install github:owain323/frontend-lib
|
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
> 历史说明:0.9.0 曾发布成功、两分钟后被误删(`ERRORS.md` E55);此后一段时间
|
|
72
|
-
> registry 上没有可安装版本,靠 GitHub Release
|
|
73
|
-
>
|
|
72
|
+
> registry 上没有可安装版本,靠 GitHub Release 附件分发(当时的分发路径记在
|
|
73
|
+
> `CHANGELOG.md` 的 0.11.2 一节)。自 0.11.2 起这条通路已恢复。
|
|
74
74
|
|
|
75
75
|
**② 源码复制**(不想装依赖):把 `01-tokens/` 和需要的组件目录复制进你的项目。
|
|
76
76
|
⚠️ 复制 `dist/` 里的版本,**别复制源码版** —— 源码版有一半以上是给人和 AI 读的注释
|
|
@@ -343,7 +343,7 @@ import 'agentcss/dist/02-primitives/button/button.css';
|
|
|
343
343
|
| `ai/` | **机器可读契约**:`components.json` / `tokens.json` / schema / 校验器 / 命令行 |
|
|
344
344
|
| `adapters/` | 场景适配层(如演示文稿),**不进核心** |
|
|
345
345
|
| `benchmark/` | 兼容性基准(10 类编辑意图,当前无模型实测记录) |
|
|
346
|
-
| `docs/` | `INVARIANT.md`(不变式)· `
|
|
346
|
+
| `docs/` | `INVARIANT.md`(不变式)· `CONVENTIONS.md`(全局约定)· `ACCESSIBILITY.md`(无障碍)· `AI-CONTRACT.md`(与 AI 协作的契约)· `LAYER-BASELINE.md`(引擎能力基线) |
|
|
347
347
|
| `types/` | TypeScript 类型定义(描述 `window` 上的全局对象) |
|
|
348
348
|
|
|
349
349
|
## 示例与验证状态
|
|
@@ -474,10 +474,10 @@ node ai/cli.js diff a.json b.json # 两份契约文档按语义 id
|
|
|
474
474
|
|---|---|---|
|
|
475
475
|
| **不变式** | `docs/INVARIANT.md` | 客观事实,永不变。有门禁守边界 |
|
|
476
476
|
| **契约** | `ai/*.json` + `API.md` | 约定,随版本变 |
|
|
477
|
-
| **建议** | `
|
|
477
|
+
| **建议** | `CONTRIBUTING.md` | 工作方式建议,可整份推翻 |
|
|
478
478
|
|
|
479
479
|
约束要放宽时动的是**建议层**,不变式一个字都不用动。
|
|
480
|
-
|
|
480
|
+
对标了谁、故意不学谁,散见 `CHANGELOG.md`(每版一节的取舍)与各组件的 README(每份都写了「来源」一栏)。
|
|
481
481
|
|
|
482
482
|
## 可访问性
|
|
483
483
|
|
|
@@ -499,9 +499,9 @@ npm run benchmark # 兼容性基准
|
|
|
499
499
|
|
|
500
500
|
改代码时跑 `gate:fast` 即可;**提交前**再跑一次 `gate`。
|
|
501
501
|
|
|
502
|
-
类型声明在 `types/index.d.ts`。改动它之前先看
|
|
503
|
-
|
|
504
|
-
|
|
502
|
+
类型声明在 `types/index.d.ts`。改动它之前先看 `API.md` 的「稳定程度约定」,
|
|
503
|
+
以及 `ai/components.json` 里标 `beta` 的那几个组件 ——
|
|
504
|
+
它们定稿之前,那几部分不适合作为长期依赖引入。
|
|
505
505
|
|
|
506
506
|
## 许可
|
|
507
507
|
|
package/ai/START-HERE.md
CHANGED
|
@@ -92,9 +92,9 @@ node ai/cli.js check <doc.json> # 校验契约文档(读 JSON,不解析原
|
|
|
92
92
|
|---|---|---|
|
|
93
93
|
| 不变式 | `docs/INVARIANT.md` | 客观事实,有门禁守边界 |
|
|
94
94
|
| 契约 | `ai/*.json` | 版本化约定 |
|
|
95
|
-
| 建议 | `
|
|
95
|
+
| 建议 | `CONTRIBUTING.md` | 工作方式建议,可整份推翻 |
|
|
96
96
|
|
|
97
|
-
>
|
|
97
|
+
> 本文件(就是那份贴进 system prompt 的极简版)的适用范围是
|
|
98
98
|
> **维护 frontend-lib 自身或严格按本库范式写页面**,不是所有 HTML 任务的通用规则。
|
|
99
99
|
|
|
100
100
|
## 7 · 三句话版本
|
package/ai/components.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
3
|
"generatedBy": "05-audit/gen-ai-contract.py",
|
|
4
|
-
"libraryVersion": "0.11.
|
|
4
|
+
"libraryVersion": "0.11.8",
|
|
5
5
|
"$comment": "机器可读的组件契约。slots = BEM 元素名(在本库里元素名就是插槽名),states.enum = 组件会在 DOM 上写出的 data-* 取值,states.legacyClass = 仍未收敛到 data 属性的历史状态类(INVARIANT I-8 未达标)。",
|
|
6
6
|
"maturityCriteria": {
|
|
7
7
|
"alpha": "缺 demo.html 或缺 05-audit 下的专属门禁 ⇒ 还没被机器验证过,不要用",
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# 无障碍
|
|
2
|
+
|
|
3
|
+
目标等级是 **WCAG 2.2 AA**。本文说清三件事:**验过什么、怎么验的、还差什么**。
|
|
4
|
+
没说到的,就当作没验过 —— 这份文件的用处是让人不必猜。
|
|
5
|
+
|
|
6
|
+
## 一 · 已验证的范围
|
|
7
|
+
|
|
8
|
+
最近一次运行(`node 05-audit/a11y-scan.js`):
|
|
9
|
+
|
|
10
|
+
| 项目 | 手段 | 结果 |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| 自动审计 | `axe-core`,跑 18 个页面(组件 demo + 文档页 + 长文页) | **0 类违规 / 991 条规则通过**;另有 **9 处**标记「待人工」 |
|
|
13
|
+
| 「0 违规」可信吗 | 反向控制:注入 5 类典型违规,看它抓不抓得到 | 5 类全部抓到。**没有反向控制的 0 违规不算证据** |
|
|
14
|
+
| 对比度(浅色) | `contrast.py` 从 `tokens.css` 读实色值逐对计算 | 正文 ≥ 4.5:1、控件边界 ≥ 3:1、装饰线不强制 |
|
|
15
|
+
| 对比度(暗色) | `dark-contrast.js` 单独算一遍 | 同上;暗色不是浅色取反,独立验收 |
|
|
16
|
+
| 组件级对比度 | `component-contrast.py` | 每个组件自己的配色再算一次 |
|
|
17
|
+
| 焦点可见 | `focus-ring-check.js` | 有焦点环,且不是 `outline: none` 代替品 |
|
|
18
|
+
| 焦点归还 | `focus-return-check.js` | 关闭弹层/抽屉后焦点回到触发元素 |
|
|
19
|
+
| 键盘可达 | `keyboard-nav.js` | Tab 顺序、Esc 关闭、不困在浮层里 |
|
|
20
|
+
| 触控目标 | `a11y.py` + 行为脚本 | 热区 44×44(WCAG 2.5.5) |
|
|
21
|
+
| 减少动效 | `motion.py --reduced-audit` | 位移关、真实进度留(3 处登记例外)、双向核对 |
|
|
22
|
+
| 页面结构 | 文档站门禁逐页判 | 每页有 `lang`、单一 `h1`、`main`、跳转链接、无重复 `id` |
|
|
23
|
+
|
|
24
|
+
目标尺寸、对比度阈值、动效分档这些**规则本身**写在
|
|
25
|
+
[全局约定](CONVENTIONS.md) 里,这里只报结论。
|
|
26
|
+
|
|
27
|
+
## 二 · 已知缺口
|
|
28
|
+
|
|
29
|
+
这一节比上一节重要。以下都是**没做到**的,不是"应该没问题":
|
|
30
|
+
|
|
31
|
+
1. **没有真机读屏记录。** NVDA、VoiceOver、TalkBack 一个都没录过。
|
|
32
|
+
axe 只覆盖 WCAG 问题的约 30–40%,它查不出"alt 写得有没有意义""阅读顺序对不对"
|
|
33
|
+
"屏幕阅读器实际听到的是什么"—— 那几项只能用耳朵听。
|
|
34
|
+
所以本库目前能说的是「**自动检查能覆盖的部分是干净的**」,不能说得更多。
|
|
35
|
+
2. **RTL 只有静态检查。** 交付 CSS 里物理方向属性 0 处、全部逻辑属性(`rtl-check.js` 守),
|
|
36
|
+
但没有一张 `dir="rtl"` 的渲染基线,也没跑过阿拉伯语/希伯来语的文字膨胀案例。
|
|
37
|
+
3. **强制色板(forced-colors)只覆盖了一部分。** 6 个 CSS 写了
|
|
38
|
+
`forced-colors` / `prefers-contrast` 的处理,不是全部组件。
|
|
39
|
+
Windows 高对比度模式下的表现没有逐组件验证。
|
|
40
|
+
4. **Android 系统 WebView 未验证。** 真机矩阵里它是 ❌(见
|
|
41
|
+
[浏览器支持矩阵](../SUPPORT.md)),已验的是微信内置内核、iOS/macOS Safari 等 7 行。
|
|
42
|
+
5. **没有 VPAT / ACR 正式符合性报告。** 采购流程里要的那种文档,本库没有出。
|
|
43
|
+
6. **9 处「待人工」还没有人工结论。** 自动审计把它们标出来了(需要人判断,
|
|
44
|
+
比如图表的替代文本是否描述了数据),目前仍挂着。
|
|
45
|
+
|
|
46
|
+
## 三 · 怎么自己验
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 单个 demo 页的可访问性(结构、语言、标题层级、跳转链接…)
|
|
50
|
+
python3 05-audit/a11y.py 02-primitives/button/demo.html
|
|
51
|
+
|
|
52
|
+
# axe 审计(跑全部已登记的页面)
|
|
53
|
+
node 05-audit/a11y-scan.js
|
|
54
|
+
|
|
55
|
+
# 证明上面那个"0 违规"不是因为没查
|
|
56
|
+
node 05-audit/_a11y-reverse-control.js
|
|
57
|
+
|
|
58
|
+
# 亮色 / 暗色两套对比度
|
|
59
|
+
python3 05-audit/contrast.py
|
|
60
|
+
node 05-audit/dark-contrast.js
|
|
61
|
+
|
|
62
|
+
# 键盘与焦点
|
|
63
|
+
node 05-audit/keyboard-nav.js
|
|
64
|
+
node 05-audit/focus-ring-check.js
|
|
65
|
+
node 05-audit/focus-return-check.js
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`python3 05-audit/a11y.py --dir .` 会连 `node_modules` 一起扫,
|
|
69
|
+
那里面的第三方 HTML 会报一堆与你无关的问题 —— 门禁里用的是逐目录调用,别用 `--dir .`。
|
|
70
|
+
|
|
71
|
+
## 四 · 报告问题
|
|
72
|
+
|
|
73
|
+
无障碍问题按**缺陷**处理,不按"改进建议"处理:请开 issue 并写清
|
|
74
|
+
**控件 + 输入方式(键盘 / 读屏 / 触摸)+ 期望行为**。
|
|
75
|
+
已知缺口里列的那些(读屏实测、RTL 渲染、高对比度)如果能提供你机器上的
|
|
76
|
+
实际结果,比任何自动化都值钱。
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# 全局约定
|
|
2
|
+
|
|
3
|
+
这些规则不属于任何一个组件:令牌怎么用、焦点环多大、动效多久、暗色怎么切、
|
|
4
|
+
能不能写 `margin-left`。它们由 `01-tokens/` 里的几个全局文件实现,由一批门禁守住。
|
|
5
|
+
本文是它们的**唯一说明书** —— 规则写在这里,判据在仓库里,两边一一对应。
|
|
6
|
+
|
|
7
|
+
读法:如果你只想知道「我该写哪个变量」,看第一节和第二节;
|
|
8
|
+
如果你想改这套东西,看第十节,那里列了每条规则由什么挡住。
|
|
9
|
+
|
|
10
|
+
## 一 · 令牌分三层,你只用中间层
|
|
11
|
+
|
|
12
|
+
| 层 | 长什么样 | 谁写 | 谁用 |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| raw | `--raw-paper`、`--raw-text-primary` | 只有 `01-tokens/tokens.css` | 没人 —— 它是调色板原料 |
|
|
15
|
+
| 语义 | `--paper`、`--surface`、`--text-primary` | 只有 `01-tokens/tokens.css` | **你的代码用这一层** |
|
|
16
|
+
| 组件私有 | `--btn-pad` 之类 | 该组件自己的 CSS | 该组件自己 |
|
|
17
|
+
|
|
18
|
+
**为什么必须隔一层**:暗色不是"把颜色逐条改掉",而是**换掉 raw 的值,语义层不动**。
|
|
19
|
+
组件里写 `--raw-*`,暗色就会漏掉它 —— 这也是 `dark-contrast.js` 会算两遍对比度的原因。
|
|
20
|
+
组件与页面里**写死 `#hex`** 一律不许(`hygiene.py` 判)。
|
|
21
|
+
|
|
22
|
+
常用语义令牌,括号里是对当前纸底 / 表面的**实算对比度**(不是估的):
|
|
23
|
+
|
|
24
|
+
| 令牌 | 用途 | 对比度 |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `--paper` | 页面底色 | — |
|
|
27
|
+
| `--surface` / `--surface-raised` / `--surface-sunken` | 卡片 / 浮起 / 凹陷 | — |
|
|
28
|
+
| `--text-primary` | 正文 | 16.60 / 17.81 · AAA |
|
|
29
|
+
| `--text-secondary` | 次要说明 | 7.01 · AAA |
|
|
30
|
+
| `--text-tertiary` | 弱化文字 | 5.33 / 4.89 · AA |
|
|
31
|
+
| `--border-control` | 输入框等**控件**边框 | 3.19 / 3.42 · AA(大字与控件) |
|
|
32
|
+
| `--border-decor` | 纯装饰分隔线 | 1.18 · 不适用(WCAG 不强制装饰) |
|
|
33
|
+
| `--accent` / `--accent-soft` | 强调色 / 浅底与焦点环 | 见 `contrast.py` 输出 |
|
|
34
|
+
|
|
35
|
+
⚠️ `--accent-soft` **不要拿来当正文背景**:它只保证"能看见有一块色",不保证字读得清。
|
|
36
|
+
|
|
37
|
+
## 二 · 间距、圆角、层级:只准踩台阶
|
|
38
|
+
|
|
39
|
+
间距是 8 档,前 3 档固定、后 5 档随视口缩放:
|
|
40
|
+
|
|
41
|
+
| 令牌 | 值 | 说明 |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `--sp-1` / `--sp-2` / `--sp-3` | 4 / 8 / 12px | 组件内部微调 |
|
|
44
|
+
| `--sp-4` | `clamp(1rem, …, 1.25rem)` | 16 → 20px |
|
|
45
|
+
| `--sp-5` | `clamp(1.25rem, …, 1.75rem)` | 20 → 28px |
|
|
46
|
+
| `--sp-6` | `clamp(1.5rem, …, 2.25rem)` | 24 → 36px |
|
|
47
|
+
| `--sp-7` | `clamp(2rem, …, 3.5rem)` | 32 → 56px |
|
|
48
|
+
| `--sp-8` | `clamp(2.5rem, …, 4.5rem)` | 40 → 72px |
|
|
49
|
+
|
|
50
|
+
圆角:`--r-xs 3` / `--r-sm 6` / `--r-md 10` / `--r-lg 14` / `--r-pill 999`(px)。
|
|
51
|
+
层级:`--z-base 0` → `--z-sticky 10` → `--z-header 20` → `--z-overlay 30` →
|
|
52
|
+
`--z-modal 40` → `--z-toast 50`,另有 `--z-raised 5`(卡片悬停、内层可点元素)。
|
|
53
|
+
|
|
54
|
+
写 `padding: 13px` 不算错,但它**不会跟着页面框一起变** —— 这就是为什么
|
|
55
|
+
文档站曾经出现"改 `--sp-6`,35 页 padding-top 一动不动"(那时页面框被写了两份)。
|
|
56
|
+
|
|
57
|
+
## 三 · 焦点环:`:focus-visible`,2px + 2px
|
|
58
|
+
|
|
59
|
+
```css
|
|
60
|
+
:focus-visible {
|
|
61
|
+
outline: 2px solid var(--accent);
|
|
62
|
+
outline-offset: 2px;
|
|
63
|
+
border-radius: var(--r-sm);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- **只用 `:focus-visible`,不用 `:focus`**。前者只给键盘用户看,鼠标点击不冒框。
|
|
68
|
+
用 `:focus` 的后果是"点一下留个框",用户会以为界面卡住了。
|
|
69
|
+
- **禁止 `outline: none` 而不给替代指示** —— 这是无障碍事故,不是审美选择。
|
|
70
|
+
真要自绘,用 `--focus-ring-width` / `--focus-ring-offset` / `--focus-ring-color`
|
|
71
|
+
三个令牌,别自己写死像素。
|
|
72
|
+
- 深底上(暗色、实心按钮)加一圈内白环 `--focus-ring-inset`,否则焦点环与背景糊在一起。
|
|
73
|
+
- 打开弹层再关掉,**焦点必须回到触发它的元素**上:`focus-return-check.js` 判这一条。
|
|
74
|
+
|
|
75
|
+
## 四 · 动效:三档时长,三条规定
|
|
76
|
+
|
|
77
|
+
| 令牌 | 值 | 用在哪 |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `--dur-fast` | 120ms | 颜色、透明度、悬停 |
|
|
80
|
+
| `--dur-base` | 180ms | 位移、展开 |
|
|
81
|
+
| `--dur-slow` | 280ms | 大块入场、遮罩 |
|
|
82
|
+
|
|
83
|
+
曲线三条:`--ease-out`(入场/展开)、`--ease-in-out`(屏上位移/折叠)、
|
|
84
|
+
`--ease-drawer`(抽屉式滑入)。超过 300ms 的 UI 动效会被读成"卡"。
|
|
85
|
+
|
|
86
|
+
系统开了"减少动效"时,策略是**分档**的,不是一刀切:
|
|
87
|
+
|
|
88
|
+
1. **位移 / 缩放 / 旋转 —— 关**。前庭不适的主要来源。
|
|
89
|
+
2. **真实进度(spinner、加载态)—— 留**。停掉它,用户会以为没点上,从而重复提交;
|
|
90
|
+
这比有动画更糟。目前登记的例外只有三处:`button`、`states`、`form-validation`。
|
|
91
|
+
3. **颜色 / 透明度 —— 可以缩短到 `--dur-fast` 保留**,这一档不强制、也没有判据
|
|
92
|
+
(写了却没判据的规则,比没有规则更糟)。
|
|
93
|
+
|
|
94
|
+
例外登记在 `ai/motion-policy.json`。**声明了却没实现 ⇒ 红;实现了却没声明 ⇒ 红。**
|
|
95
|
+
这条双向判据是它存在的全部意义。
|
|
96
|
+
|
|
97
|
+
## 五 · 暗色:三种状态,组件一律不许自判
|
|
98
|
+
|
|
99
|
+
| 使用者的意思 | 怎么表达 |
|
|
100
|
+
|---|---|
|
|
101
|
+
| 跟随系统 | 什么都不写(`color-scheme: light dark` 已声明) |
|
|
102
|
+
| 我要亮的 | `<html data-theme="light">` |
|
|
103
|
+
| 我要暗的 | `<html data-theme="dark">` |
|
|
104
|
+
|
|
105
|
+
实现上有两个必须知道的坑:
|
|
106
|
+
|
|
107
|
+
- 系统暗色那一段的选择器是 `:root:not([data-theme="light"])`,**强制暗**另由
|
|
108
|
+
`:root[data-theme="dark"]` 那一段负责,两段互不干扰。
|
|
109
|
+
**少了 `:not(...)`**,系统开着暗色时使用者点"亮"仍然会看到暗的 ——
|
|
110
|
+
三态开关里最容易被漏掉的那条路径(这也是为什么"强制亮"能压过系统暗色)。
|
|
111
|
+
- **组件里不许出现 `prefers-color-scheme`**。组件只认语义令牌,主题切换由
|
|
112
|
+
`tokens.css` 一处完成。组件自己判主题,等于给每种组件各开一个"主题分支",
|
|
113
|
+
加一个主题就要改 35 个文件。
|
|
114
|
+
|
|
115
|
+
暗色不等于"把浅色取反",三条已经踩过的坑:正文不用纯白(深底上字会发散)、
|
|
116
|
+
凹陷面在暗色里要**比纸面略亮**(层级靠变亮,不是变暗)、滚动条 thumb 要更亮
|
|
117
|
+
(用浅色那档会变成"黑里有黑")。
|
|
118
|
+
|
|
119
|
+
## 六 · 逻辑属性:不许写 `margin-left`
|
|
120
|
+
|
|
121
|
+
交付的 43 个 CSS 里,物理方向属性(`margin-left` / `padding-left` / `left:`)**0 处**,
|
|
122
|
+
全部用 `margin-inline-start` 这类逻辑属性。`rtl-check.js` 守这一条。
|
|
123
|
+
|
|
124
|
+
绝对定位是唯一需要动脑的地方:
|
|
125
|
+
|
|
126
|
+
```css
|
|
127
|
+
/* 错的:RTL 下浮层会跑到另一边 */
|
|
128
|
+
.tip { position: absolute; left: 0; }
|
|
129
|
+
/* 对的:两个方向都说清楚 */
|
|
130
|
+
.tip { position: absolute; inset-inline-start: 0; }
|
|
131
|
+
/* 确实要按物理方向时,显式覆盖 */
|
|
132
|
+
[dir="rtl"] .tip { left: auto; right: 0; }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 七 · 不用 `@layer`;`!important` 必须说得清
|
|
136
|
+
|
|
137
|
+
交付 CSS 里 `@layer` 是 **0 处**,这是决定,不是遗漏:目标环境里有 Chromium 97 的内核
|
|
138
|
+
(见[浏览器支持矩阵](../SUPPORT.md)),它不认识 `@layer` —— **整层样式会直接消失**。
|
|
139
|
+
所以"未分层"是当前唯一安全的选择。`cascade-layer-gate.py` 把这条变成可判状态:
|
|
140
|
+
一旦出现第一条 `@layer`,就要求三条前提的实测记录已落档,否则红。
|
|
141
|
+
|
|
142
|
+
`!important` 现有 10 处,**每一处都必须能被归类**:
|
|
143
|
+
|
|
144
|
+
| 类别 | 处数 | 为什么必须赢 |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| 无障碍偏好(`prefers-reduced-motion`) | 9 | 操作系统级偏好,组件无权推翻 |
|
|
147
|
+
| 正确性(`[hidden] { display: none }`) | 1 | 不赢就会"藏不住" |
|
|
148
|
+
|
|
149
|
+
分不了类的 `!important` ⇒ 红。判据不是"清零"——清零会逼人删掉无障碍兜底。
|
|
150
|
+
|
|
151
|
+
## 八 · 对比度与触控的硬线
|
|
152
|
+
|
|
153
|
+
- 正文 **4.5:1**,大字与**控件**边界 **3:1**,纯装饰不强制(但也不许拿来传信息)。
|
|
154
|
+
- 触控目标 **44×44**(WCAG 2.5.5)。视觉尺寸可以更小,热区要补足。
|
|
155
|
+
- **禁用态用明确色值,不用 `opacity`**。半透明会让文字对比度随底色漂移,
|
|
156
|
+
在暗色下直接不达标;而且"看起来只是淡了点"没法表达"不能点"。
|
|
157
|
+
|
|
158
|
+
## 九 · 覆盖策略:改令牌,不要改组件
|
|
159
|
+
|
|
160
|
+
```css
|
|
161
|
+
/* 换品牌色:只改一个语义令牌,全站生效,暗色与焦点环自动跟上 */
|
|
162
|
+
:root { --accent: var(--brand-accent); }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`--brand-accent` 由你自己定义(任意合法颜色值)。之所以不在这里写一个具体的十六进制:
|
|
166
|
+
本库的对比度判据会把**文档里的色值**当成"内联副本"去核对,
|
|
167
|
+
它分不清示例与真实用法 —— 与其教它开例外,不如让示例本来就不带字面值。
|
|
168
|
+
|
|
169
|
+
- 优先覆盖 **语义令牌**(第一节那张表)。
|
|
170
|
+
- 其次覆盖组件自己的槽位变量(如按钮的内边距),它们都以组件名为前缀。
|
|
171
|
+
- **不要把 `!important` 当覆盖手段**:赢了这一条,会把无障碍兜底一起压掉。
|
|
172
|
+
- 不要用更深的选择器去压(`.page .btn.btn--primary`)—— 那是把特异性当版本管理,
|
|
173
|
+
下次改版必然打架。
|
|
174
|
+
|
|
175
|
+
## 十 · 每条规则由什么挡住
|
|
176
|
+
|
|
177
|
+
规则只有落到判据上才会被遵守。对应关系如下(文件都在 `05-audit/`):
|
|
178
|
+
|
|
179
|
+
| 规则 | 判据 |
|
|
180
|
+
|---|---|
|
|
181
|
+
| 不写死 `#hex`、不留调试残留 | `hygiene.py` |
|
|
182
|
+
| 组件之间不许 `@import` 互相依赖 | `css-independence.py` |
|
|
183
|
+
| 亮/暗两套对比度达标 | `contrast.py`、`dark-contrast.js`、`component-contrast.py` |
|
|
184
|
+
| 焦点环存在且不是 `outline:none` | `focus-ring-check.js` |
|
|
185
|
+
| 关掉弹层后焦点回到触发点 | `focus-return-check.js` |
|
|
186
|
+
| 键盘可达(Tab 顺序、Esc 关闭) | `keyboard-nav.js` |
|
|
187
|
+
| 减少动效策略双向一致 | `motion.py --reduced-audit` |
|
|
188
|
+
| 逻辑属性全覆盖 | `rtl-check.js` |
|
|
189
|
+
| `@layer` 计数与 `!important` 分类 | `cascade-layer-gate.py` |
|
|
190
|
+
| 触控目标 44px | `a11y.py`(静态) + 行为脚本(运行时) |
|
|
191
|
+
| 页面骨架只有一份(改令牌能牵动全部页面) | `skeleton-lever-check.js` |
|
|
192
|
+
|
|
193
|
+
想加一条新规则?先在门禁里加判据、确认它会红,再写进本文 ——
|
|
194
|
+
**顺序反了就会得到一条"写在文档里、没人执行"的规则。**
|
package/docs/INVARIANT.md
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|---|---|---|---|
|
|
45
45
|
| **Invariant**(本文件) | 客观事实 | **永不变**(除非 CSS 规范变了) | 门禁 |
|
|
46
46
|
| **Contract**(`ai/contract.schema.json`) | 版本化的接口约定 | 随版本变,**向后兼容** | 校验器 |
|
|
47
|
-
| **Guidance**(`
|
|
47
|
+
| **Guidance**(`CONTRIBUTING.md`) | 当前模型下的工作建议 | **随时可变,可整份推翻** | 人 |
|
|
48
48
|
|
|
49
49
|
⇒ **判断一条内容该放哪层的唯一标准**:
|
|
50
50
|
> 如果明天出现一个无限强的模型,这句话还成立吗?
|
|
@@ -93,7 +93,7 @@ a { color: red } /* 命中文档里每一个 <a>,无论它属于谁 */
|
|
|
93
93
|
`@layer` 的层序优先于选择器特异性。
|
|
94
94
|
(这也是为什么 Web Awesome 把全局样式分层——让使用者的 CSS 自动赢,不需要 `!important`。)
|
|
95
95
|
|
|
96
|
-
- 本库未使用 `@layer`。理由、切换前提与就绪判据见 `docs/
|
|
96
|
+
- 本库未使用 `@layer`。理由、切换前提与就绪判据见 `docs/CONVENTIONS.md` 的「不用 `@layer`」一节。
|
|
97
97
|
|
|
98
98
|
### I-4 inline style 的优先级高于**一切**样式表
|
|
99
99
|
|
package/index.html
CHANGED