设计 Token 的 border-radius 把 Checkbox 变成了圆形
组件库搭到一半,截个图,Checkbox 和 Radio 长得一模一样——全是圆的。DOM 没报错,单元测试全绿,data-checked 属性也正确。只有像素截图把这个坑暴露出来。
现象
某后台管理系统正在接入一套设计 token 驱动的组件库,全局 --radius 设为 0.625rem(10px),统一用 rounded-[var(--radius)] 覆盖所有组件以保持一致性。大尺寸组件(卡片、输入框、对话框)显示正常,但 Checkbox 渲染出来是一个完美圆形,与同页面的 Radio 在视觉上无法区分。
根因
CSS 有一条硬规则:当 border-radius ≥ min(width, height) / 2 时,元素渲染为圆形(或椭圆)。
Checkbox 通常是 size-5(20px 正方形)。全局 token --radius: 0.625rem = 10px,恰好等于 20px / 2——正好踩上圆形阈值。
这个问题对单元测试和 DOM 断言完全透明:
- 类名已挂上
rounded-[var(--radius)]✓ data-checked属性正确 ✓- 组件功能正常 ✓
- 视觉形状:圆形 ✗(但没有任何测试捕获这个维度)
--radius 通常是按卡片、输入框等中大尺寸组件标定的。对 16–24px 的小方形控件(Checkbox、图标按钮、方形徽章)直接套用,极易超过 side/2 的圆化临界值。超过之后没有任何视觉过渡——就是一个圆,和 Radio 无法区分。
正解
用 CSS min() 函数对 token 封顶,让圆角仍然跟随 token 变化,但不允许超过小控件的圆化阈值:
// ❌ size-5 (20px) 控件,--radius=10px → border-radius:10px → 完美圆形
"... rounded-[var(--radius)] ..."
// ✅ 封顶在 0.375rem (6px),token 值小时跟随,token 值大时保持方角
"... rounded-[min(var(--radius),0.375rem)] ..."选封顶值的经验法则:cap < min(width, height) / 2。对 20px 控件,0.375rem(6px)有足够余量,视觉上是清晰的圆角方形(squircle)。
Tailwind v4 的任意值语法支持 min() 里的逗号,rounded-[min(var(--radius),0.375rem)] 可以正确编译为 border-radius: min(var(--radius), 0.375rem)。Tailwind v3 的任意值解析对逗号处理有时不稳定,需留意。
Radio、Avatar 这类本就该是圆的控件保持 rounded-full 不动——改造目标只是防止方形控件意外变圆。
rounded-[min(var(--radius),0.375rem)] 让圆角跟随 token 缩放,但永远不会超过小控件的圆化临界。比直接硬编码 rounded-[6px] 好——后者会在用户切换主题 radius 时失去响应性。
排查与验证
截图验证,不要只看 DOM。渲染 Checkbox 和 Radio 并排,用真实浏览器截图对比角落形状;聚焦放大区域可以让圆角差异一目了然。
快速在 DevTools 里验证当前是否已成圆:
const el = document.querySelector('[role=checkbox]');
const r = parseFloat(getComputedStyle(el).borderRadius);
const side = el.getBoundingClientRect().width;
console.log(r >= side / 2 - 0.5); // true → 已是圆形(Checkbox 的 bug)一句话外卖
面向大控件标定的 radius token,在小方形控件上直接用会静默超过圆化阈值;用
min(var(--radius), 封顶值)封顶,token 响应性和正确形状两者兼得。