表面
本技能决定每个元素由什么构成:其填充、边缘、阴影、圆角、状态和图标。它从一份简短的配方列表中挑选,因此两个屏幕上绝不会出现两种不同的卡片。
在做任何决策之前,请应用 product-ui §1(品味日志和优先级)和 §5(哪个技能拥有什么)。
按此顺序决策
- 是否需要表面? 先移除再添加。带周围空白的标题不需要卡片。永远不要在卡片内嵌套卡片。原因:每个容器都会增加一个眼睛必须阅读的边缘。
- 哪个层级? 页面、抬升、浮动或覆盖。每个元素一个层级,根据其功能而不是你想让它多突出而选择。elevation.md
- 边缘由什么绘制? 高度获得阴影环。结构获得边框。绝不在一个边缘上同时使用两者。elevation.md
- 哪个圆角? 获取元素角色的标记,然后使嵌套的圆角同心。elevation.md
- 哪些状态? 将悬停、按下、选中、禁用和焦点可见设计为表面的变化,然后再完善默认状态。states-and-icons.md
- 哪种图标处理? 单一集合、
currentColor、描边与文本匹配。states-and-icons.md - 是否允许材质? 仅限滚动内容上方的浮动镀铬。materials-and-edges.md
- 滚动边缘。 分隔线、淡化、过滚动。materials-and-edges.md
四个层级
| 层级 | 用于 | 浅色模式 | 深色模式 |
|---|---|---|---|
| 页面 | 底面 | 底面填充,无阴影 | 最深填充,无阴影 |
| 抬升 | 卡片、控件、选中项 | 白色,环 + 1px 投影 + 3px 紧密投影 | 亮一个色阶,白色环,紧密黑色投影 |
| 浮动 | 菜单、弹出层、工具提示、提示框 | 白色,环 + 1px 投影 + 4px 紧密投影 | 亮两个色阶,白色环,紧密黑色投影 |
| 覆盖 | 对话框、抽屉、命令面板(带遮罩) | 白色,环 + 1px 投影 + 6px 紧密投影 | 亮三个色阶,白色环,紧密黑色投影 |
精确标记在 elevation.md 中。其背后的规则:
- 环加紧密投影。 模糊 ≤ 8px,偏移 ≤ 6px,投影具有负扩散。原因:大而柔和的模糊读起来像发光,弄脏页面,看起来像生成的。
- 深色模式通过变亮来抬升。 阴影在深色上几乎看不见,因此亮度承载高度,低 Alpha 的白色环承载边缘。
- 阴影是半透明的且单一颜色。 不透明的灰色会留下光晕。深色投影保持黑色。
- 同级共享一个层级。 一行中的卡片有一个阴影、一个圆角、一个内边距。
- 悬停改变填充,而不是阴影。
无需参考的规则
- 边框显示结构,阴影显示高度。 分隔线、输入框、表格单元格和选中状态保留边框。一个卡片其边框仅为了抬升而存在时,应使用环代替。
- 在阴影环下方保留
border: 1px solid transparent。 原因:强制色彩模式会移除阴影,而边框仍会绘制一个边缘。 - 图像获得 1px 内嵌轮廓:
outline: 1px solid黑色 10%(深色下白色 10%),outline-offset: -1px。绝不要使用色调颜色,后者会显示为条纹。跳过徽标和透明艺术品。 - 同心圆角。 外层 = 内层 + 内边距(+ 边框),最多 24px 内边距。超过此值,它们就是独立的表面。
- 焦点是一个环,绝不要无。
:focus-visible显示一个 2px 的强调色轮廓,偏移 2px;字段采用齐平的方式,作为 2px 的强调色边缘,没有间隙。没有替代品的outline: none是个 bug。该环不占用布局空间。 - 禁用控件不接受悬停、按下或缩放。
- 图标:每个表面一个集合,
currentColor,默认轮廓,激活时填充。描边与相邻文本字重匹配,400 字重时 1.5px 到 600 字重时 2px。 - 仅浮动镀铬中使用背景模糊。 绝不要在内容、卡片或表格上使用。
- 可滚动覆盖层(对话框、抽屉、菜单)获得
overscroll-behavior: contain。
对比
| 错误的写法 | 正确的写法 | 原因 |
|---|---|---|
卡片上的 border: 1px solid #e5e5e5 | box-shadow: var(--elev-raised); border: 1px solid transparent | 仅用于深度的边框变成环;强制色彩仍会绘制边缘 |
box-shadow: 0 10px 40px rgba(0,0,0,.2) | 0 0 0 1px a.08, 0 1px 2px a.12, 0 4px 8px -4px a.16 | 紧密清晰胜过宽大柔和 |
深色卡片使用 0 8px 24px #000 | 更亮的填充加 0 0 0 1px oklch(1 0 0 / .08) | 阴影在深色上消失;亮度显示高度 |
| 卡片圆角 8,内部图片圆角 8,内边距 8 | 内部 8,外部 16 | 同心角保持均匀间隙 |
<img> 融入白色卡片 | outline: 1px solid oklch(0 0 0 / .1); outline-offset: -1px | 边缘可读,无需边框 |
button:focus { outline: none } | button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px } | 键盘用户保留焦点 |
| 1.5px 图标描边紧邻 600 字重标签 | 2px 描边 | 描边粗细匹配字重粗细 |
| 每个工具栏位置填充图标 | 默认轮廓,激活按钮填充 | 填充是状态信号 |
卡片上的 backdrop-filter: blur(20px) | 不透明表面 | 模糊属于浮动在滚动内容上的镀铬 |
完成前检查
| 模式 | 修复 |
|---|---|
box-shadow 模糊 > 8px 或 Alpha > .2 在浅色 UI 上 | 使用层级标记 |
border 加上 box-shadow 做同样的工作 | 仅使用环加透明边框 |
阴影环缺少 border: 1px solid transparent | 添加它 |
父元素和其内边距子元素相同的 border-radius | 父元素使用 inner + padding |
没有 :focus-visible 替代品的 outline: none 或 outline: 0 | 添加环 |
使用 :focus(不是 :focus-visible)样式化环 | 切换到 :focus-visible |
状态中 border-width 变化 | 颜色、填充或等宽的透明边框 |
图标中硬编码的 fill="#…" 或 stroke="#…" | 使用 currentColor |
| 一个屏幕上导入两个图标库 | 只用一个 |
固定栏、侧边栏或浮动菜单外部的 backdrop-filter | 移除它 |
覆盖层列表没有 overscroll-behavior: contain | 添加它 |
滚动器边缘的硬切,或被 overflow: hidden 裁剪的阴影 | 淡化边缘;为滚动器增加内边距 |
通过在 2 倍缩放下渲染浅色和深色模式并裁剪每个层级的边缘来进行验证。悬停、按下、Tab 聚焦、选择和禁用每个控件,并比较操作前后的盒子尺寸。切换强制色彩模式(Chrome 开发者工具,渲染仿真)并确认每个卡片仍有一个边缘。