“卡片的 border-radius 用 4px 还是 8px?”
这是一个持续了 47 分钟的讨论串。四名工程师、两名设计师、一个包含六种变体的 Figma 文件。讨论涵盖了 iOS 惯例、Material Design 指南、视觉对齐,甚至一度扯到了黄金比例。没有人是错的。这恰恰就是问题所在。
类似的场面我们已经经历过很多次了。不只是 border-radius——渐变方向、阴影模糊值、禁用状态的透明度,全都一样。每个决策都很小,每场讨论都很合理。但累积起来,它们正在蚕食我们的开发速度。
于是我们不再试图找到正确答案,而是直接移除了这个问题。
选项的代价
设计系统中的每一个视觉属性都是一个决策面。单是 border-radius 就能引出一连串问题:按钮和卡片应该用同样的值吗?嵌套元素怎么办——内层圆角要减去外层的吗?不同尺寸下圆角是否需要调整?
这些问题都不难。正因如此,它们才危险。它们简单到人人都有自己的看法,却又无关紧要到没有任何看法能被证明更优。这是经典的”自行车棚效应”(bikeshedding)。
我们花了一个季度手动标注 PR 评论。结果发现,大约 30% 的设计系统评审讨论串都在争论与可用性完全无关的装饰性数值。不是布局,不是无障碍性,不是交互模式——纯粹是……美学问题。
于是,我们制定了规则。
为什么是「消除」而不是「约束」
显而易见的替代方案是保留属性但限制其取值。大多数成熟的设计系统都是这么做的——允许三种 border-radius 值、一套阴影层级体系、一组精选的渐变色板。我们也考虑过这种方案。
问题在于,受约束的选项仍然会产生争论。“这个卡片用 radius-sm 还是 radius-md?“本质上是同一场对话,只是答案变少了。少比多好,但零比少更好。约束一个属性能减少决策,消除一个属性则能彻底移除决策。
我们也研究过代码检查方案——用 ESLint 规则拒绝未授权的值。但一个只允许”0、4 或 8px 圆角”的 linter 仍然留下了三种选择。我们的目标不是让决策变得更容易,而是让决策变得不必要。
四条规则
Beaket 的组件库有四条视觉约束:
- 禁止 border-radius。 一切都使用直角。按钮、卡片、输入框、对话框、工具提示——全部是矩形。
- 禁止渐变。 只用纯色。每个表面都由单一 CSS 变量定义。
- 禁止模糊阴影。 不使用带有 blur radius 的
box-shadow。只允许偏移阴影。 - 禁止装饰性透明度。 禁用状态通过颜色变化来表现,而非透明度。不允许用
opacity: 0.5偷懒。
这些不是指导建议,而是硬性规则,在代码评审中强制执行,并记录在我们的组件指南中。添加 rounded-md 的 PR 会被直接拒绝。
为什么偏偏是这四个属性?因为它们产生的争论最多,对可用性的影响却最小。间距和排版影响可读性,颜色承载语义。但 border-radius 是 4px 还是 8px?模糊阴影是 4px 还是 6px?我们找不到哪怕一个案例证明这些选择对产品的使用体验有可衡量的影响。
采用规则之后,设计系统相关的 PR 推进速度明显加快了。评审讨论串变短了。设计师不再为同一个组件制作仅在圆角处理或阴影深度上有所不同的多个变体了。争论不是减少了——而是消失了。
我们用什么来替代
移除选项不等于移除视觉层次。你仍然需要传达深度、状态和交互。以下是我们替代每个属性的方式。
用偏移阴影替代模糊阴影
我们不使用 box-shadow: 0 4px 6px rgba(0,0,0,0.1),而是使用定义为 CSS 变量的硬偏移阴影:
--shadow-offset: 1px 1px 0px 0px var(--color-chrome);
--shadow-offset-hover: 2px 2px 0px 0px var(--color-chrome);
--shadow-offset-active: 0px 0px 0px 0px var(--color-chrome);
三种交互状态,零模糊。这就是整个阴影系统。
默认状态有 1px 偏移——元素微微浮于表面之上。悬停时推至 2px——一种细微的抬升。按下时归零——元素被压平。这是一个小巧的、机械式的动画,清晰地传达交互意图,开发者无需做任何配置。
在实践中,每个交互式组件都应用相同的三个令牌。以下是我们按钮组件的基础部分(从完整的 CVA 字符串简化而来,完整版还包含禁用和聚焦状态):
const buttonVariants = cva(
[
"inline-flex items-center justify-center gap-2",
"font-medium cursor-pointer",
"shadow-offset hover:shadow-offset-hover active:shadow-offset-active",
"transition-shadow duration-100",
// ... disabled, focus-visible, and icon styles
].join(" "),
// ... variants
);
不需要决定一个组件”配得上”哪个阴影级别。
通过 CSS 变量实现纯色方案
我们的调色板——我们称之为”Porcelain”——包含 14 个中性色,从 graphite(#030509)到 paper(#ffffff)。它们偏冷色调,带有蓝色底色。文档中的主题描述是:“工业级精度。黑色合成漆镌刻在控制面板上。纯白、冷蓝黑墨、幽灵般的阴影、青绿色点缀。”
每个颜色引用都通过语义化令牌:
--color-text-primary: var(--color-graphite);
--color-text-secondary: var(--color-steel);
--color-bg-primary: var(--color-paper);
--color-border-default: var(--color-chrome);
我们避免使用 Tailwind 的默认颜色,如 bg-white 或 text-gray-500。每个颜色都是命名变量,每个变量都映射到一个角色。没有人需要争论某处应该用 gray-100 还是 gray-200——语义化令牌已经给出了答案。
处处直角
这一条不需要替代方案。你只需要……不添加 border-radius 即可。
让我们感到意外的是,直角与偏移阴影的配合效果出奇地好。圆角卡片搭配偏移阴影看起来很奇怪——阴影的硬边与柔和的圆角相互冲突。而直角卡片搭配偏移阴影看起来浑然天成。几何形状是一致的。这些约束以我们未曾完全预料到的方式相互强化。
通过做减法,视觉语言变得统一。当一切都是直角时,没有任何形状需要为自己的存在辩护。
约束的复合效应
这种方法最显著的好处体现在横切关注点上——即涉及每一个组件的通用模式。
禁用状态在各种组件库中出了名地不一致。有的用透明度,有的改背景色,有的两者都用。我们只有一种模式,统一应用于所有组件:
disabled:shadow-none disabled:cursor-not-allowed disabled:border-dashed disabled:border-chrome disabled:bg-frost disabled:text-steel
按钮、输入框、下拉选择、复选框、开关——全部一样。移除阴影(交互可供性消失),虚线边框,frost 背景色,steel 文字色。你见过一个禁用组件的样子,就见过了所有的。这些约束消除了为每个组件的禁用状态单独”适配”其视觉个性的冲动。当组件本身没有视觉个性时,禁用状态自然就趋于一致了。
聚焦环也遵循同样的原则。每一个可聚焦的组件都使用:
focus-visible:outline-2 focus-visible:outline-signal-blue focus-visible:outline-offset-2
一种颜色、一种宽度、一种偏移。使用键盘在表单中导航的用户,在每个元素上看到的都是完全相同的聚焦环。没有组件会覆盖它,没有变体会移除它。结合统一的禁用状态模式,用户始终能准确辨识”不可用”和”已聚焦”的样子——无论他们正在与哪个组件交互。
唯一的例外
单选按钮使用了 rounded-full。
这就是整个组件库中唯一的例外。它被记录在我们的组件指南中,紧挨着它所违反的规则,因为即使是例外,也应该是经过深思熟虑的决策,而非偶然。
方形的单选按钮会让用户困惑。圆形已经如此深入地嵌入到界面惯例中,移除它就是在为了美学纯粹性而牺牲可用性。我们认为这是一笔不划算的交易。
你可能会问:如果可用性能为单选按钮破例,为什么头像、标签或切换开关不行?我们测试过了。方形头像搭配偏移阴影看起来像个人资料卡——给人一种刻意设计的感觉。标签和切换开关不像单选按钮那样受制于惯例。圆形的可供性对于单选按钮来说是独一无二的强烈;其他任何元素都没有达到这个门槛。
我们的目标从来不是”为了粗野主义而粗野主义”,而是移除那些无法证明其复杂性价值的视觉决策。圆形的单选按钮配得上它的形状,圆角的卡片不配。
这实际上要付出什么代价
如果我们说这种方法没有缺点,那是在说谎。它确实有。
“看起来没做完。” 这是来自用户测试的真实反馈。直角和纯色传递不出圆角、带阴影界面所具有的那种精致感。我们不得不多次解释背后的设计理念。
某些组件感觉很生硬。 工具提示和弹出层尤其如此,直角让它们显得更加笨重。模糊阴影的存在自有其道理——它创造出一种悬浮感,而偏移阴影无法完全复现。
第三方组件会打破这种模式。 日期选择器、富文本编辑器、图表库——它们全都自带 border-radius 和模糊阴影。我们尽可能地覆盖样式,但不得不接受边缘处的一些不一致。这是一个持续存在的矛盾,并非已解决的问题。
设计师的表达空间受到了限制。 如果你是那种享受用渐变叠加和微妙纵深感来精心打磨微交互的设计师,这套系统会让你感到束缚。这正是我们的意图——但对创作满足感来说,这确实是实实在在的代价。
它很两极分化。 有些工程师喜欢这种简洁,也有人觉得太教条了。我们没有完全化解这种分歧,只是认定一致性的收益超过了我们产品在美学上付出的代价。
我们尚不知道的事
我们还没有在真正的大规模场景中测试过这套系统——数百个组件、数据密集型界面、需要看起来原生的第三方集成。表格、图表和仪表盘可能需要偏移阴影无法提供的深度提示。
我们确实不确定用户能否像理解传统层级系统那样,对我们的阴影状态(默认 → 悬停 → 按下)建立同样的直觉认知。我们没有观察到可用性退化,但也没有专门针对这一点做过正式的 A/B 测试。目前为止一切正常。但”目前为止”只是一个很小的样本。
约束即决策
这套系统中的每一条约束,都是一个只需做一次、以后永远不需要再做的决策。禁止 border-radius 不是一种美学偏好——它是整个组件库的一份 ADR(架构决策记录)。
如果你的团队正在花时间争论那些不影响可用性的视觉属性,可以考虑直接移除那个选项。你会发现,你对它的想念远比预想的少。