圆角 token 门禁:从散落的 rounded 到可验证的设计尺度
Eyrie · HEAD...working tree · 2026-07-12 · 自包含,读完即弃
写法说明:本文按「逐跳走读」展开。代码片段取自当前工作树并经裁剪;青色斜体是本文解释,灰色斜体保留源码注释。每条旅程结尾都有排查路标。
1TL;DR
这次变更把 renderer 中混用的 rounded-md、rounded-lg 和任意值收敛到一张由 Tailwind 配置声明的数值圆角表。门禁从配置读取允许列表,再分别解析 TS/TSX 类名与 CSS 声明。
新增 rounded-[...]、未定义旧 token、动态 rounded-${value} 或 CSS 原始 border-radius 时,检查命令会打印文件、行号和原因,并以非零状态结束。
2变更地图
49 个文件共变更 +401/−89 行,其中 60 行是测试。主要体量来自已有 UI 类名的等值迁移;真正承载设计的是检查器、token 配置与测试。
| 设计重心 | 可放心略过 |
|---|---|
check-radius-tokens.mjs、tailwind.config.ts、token CSS 与测试 | renderer 中同值的 rounded-md → rounded-6 等机械替换 |
3旅程:一个 rounded 类如何变成 CI 结果
从一段 TSX 的 className 开始,先确定什么合法,再确定在哪里找,最后把违规变成可定位的 CI 输出。
tailwind.config.ts→AST
findRadiusClassViolations→Git 文件
listScannedFiles→CI 结果
runRadiusTokenCheck
3.1允许列表先于扫描
配置声明设计像素尺度,类名和 CSS 变量共享同一来源;例如 rounded-6 最终引用 var(--radius-6)。
borderRadius: {
'2.5': 'var(--radius-2\\.5)',
'6': 'var(--radius-6)',
'8': 'var(--radius-8)',
'16': 'var(--radius-16)',
full: 'var(--radius-full)',
}
loadRadiusTokens 用大括号深度定位配置表并提取键名,避免检查器复制一份会漂移的 token 清单。
3.2语法树只看静态字符串
ts-morph 找到字符串字面量和模板字面量片段,注释里的示例不会被当成真实类名。插值前若留下 rounded- 前缀,则报告为动态违规。
const staticKinds = [SyntaxKind.StringLiteral, SyntaxKind.NoSubstitutionTemplateLiteral]
for (const kind of staticKinds) {
for (const node of sourceFile.getDescendantsOfKind(kind)) {
violations.push(...scanStaticText(node.getLiteralText(), node.getStartLineNumber(), allowedTokens))
}
}
3.3失败输出保持可行动
入口只枚举 Git 已跟踪的 renderer 文件;违规输出文件、行号、原因和值。没有违规时静默退出 0,这与现有颜色、px、字号门禁一致。
if (violations.length === 0) return true
console.error('Radius-token violations in the renderer:')
for (const violation of violations) console.error(` - ${violation}`)
return false
排查路标 · 类名旅程
| 症状 | 从哪下手 |
|---|---|
rounded-md 被拒绝 | tailwind.config.ts 的 borderRadius 与调用组件 |
| 动态圆角被拒绝 | findRadiusClassViolations 的模板边界判断 |
| 命令没有输出 | runRadiusTokenCheck:静默 + 0 表示没有违规 |
4旅程:CSS 声明与组件边界
Tailwind 类不是唯一入口。CSS 直接写 border-radius 也可能绕过 class 门禁,因此第二条旅程从 PostCSS 声明节点开始。
4.1标准半径属性必须引用 token
PostCSS 解析声明节点,识别四角和逻辑方向属性;值必须由已知 var(--radius-*) 组成。Incremark 覆盖因此使用 var(--radius-6) 与 var(--radius-8)。
const tokens = cssRadiusTokens(declaration.value)
if (tokens.length > 0 && tokens.every((token) => token !== undefined && allowedTokens.has(token))) {
return
}
violations.push({ value: `${declaration.prop}: ${declaration.value}` })
4.2shadcn 的边界是源码所有权
仓库里的 components/ui 是复制进来的 shadcn 风格源码,可以被项目修改,因此和 features 一样被扫描。真正的外部依赖位于 node_modules,不属于 Git 跟踪的 renderer 源码。
components/uinode_modules / 非 renderer排查路标 · CSS/边界旅程
| 症状 | 从哪下手 |
|---|---|
| CSS 原始值被拒绝 | findCssRadiusViolations 与对应 CSS 声明 |
| 外部组件代码没有报错 | listScannedFiles 的 renderer 前缀与 Git 跟踪状态 |
5心智模型补丁
sm/md/lg 就是设计规范。现在:合法圆角由 theme.borderRadius 明确声明。rounded-[...] 是门禁错误。6新词表
| 圆角门禁 | |
|---|---|
radius token | Tailwind 类与 CSS 变量共同引用的命名圆角值。 |
allowedTokens | 从 Tailwind 配置动态读取的合法 token 集合。 |
dynamic radius | 圆角后缀由模板插值拼出,静态检查无法证明合法。 |
renderer boundary | Git 跟踪的 renderer CSS/TS/TSX 范围。 |
7测试与风险地图
- 合法数值 token 与方向 variant
- 任意值和旧 token 的失败诊断
- 模板插值动态圆角
- PostCSS 半径属性
- 真实 renderer 全量基线
- 🟠复杂动态拼接可能没有可识别字面片段
- 🟡inline style 的运行时圆角不在静态门禁范围
- 🟡自定义属性本身不作为标准半径声明检查
bun run check;圆角检查已接入主检查链。局部命令静默成功表示没有违规,不表示没有扫描。8验收提示
- class 改名是等值迁移,不代表视觉尺寸变化。
- 没有违规时命令静默 + exit 0,这是现有门禁约定。
- 构建中的其他 Tailwind 警告不属于圆角门禁输出。
9覆盖声明
本文精读了圆角配置、检测器、测试、CI 接线、CSS token 和代表性迁移文件;其余同值替换按统一映射归纳。未进行 code review,也没有把外部依赖源码当作本地变更阅读。