hello!大家好!今天给大家分享一个关于 CSS 的小技巧!
如果你经常写 Card、Popover、Dropdown 或 Modal,可能会遇到一个很微妙的问题:
组件明明已经加了圆角、阴影和白色背景,但总感觉少了一点高级感,仔细观察会发现,问题往往出在边缘。
很多项目都会习惯性地给浮层组件加上一条 1px 边框,再配合阴影来增强层级感。在浅色背景下,这种写法的边框和阴影很容易挤在同一个视觉区域,最后形成一圈固定的灰边,让边缘显得有些生硬。
后来我发现,对于这类主要依靠阴影表达层级的组件来说,边界未必需要单独交给 border。把这层极细的轮廓放进阴影体系里,往往能得到更自然的结果。
这篇文章就聊聊一个很小、但对界面质感影响很明显的技巧:为什么有些设计系统会用 hairline shadow 代替 border,以及它背后的视觉逻辑。
好了,我们直接开始!
在 Web UI 里,Card、Popover、Dropdown、Modal 这类浮层组件经常会遇到一个很小但很影响质感的问题:边缘发灰。
常见写法通常是这样:
这段 CSS 在浅灰背景上,尤其是白色 Surface 搭配多层阴影时,它很容易产生一个副作用:border 和 box-shadow 在视觉上挤到同一个边缘区域,最后看起来像一圈固定的灰边。
补充说明一下,在设计系统里,下面这些通常都属于 Surface:
Card(卡片) Modal(弹窗) Drawer(侧边栏) Panel(面板) Dropdown(下拉菜单) Popover(浮层)
言归正传,也就是这个判断:如果一个元素的边界是在表达视觉层级,就不要急着用 border。把 1px 的 hairline 放进 shadow 层,往往比独立 border 更稳定。
问题不在 border,而在语义不匹配
border 本身没有问题。它适合表达明确的几何边界,例如:
input 的默认边框; table cell 的分割线; list item 之间的分隔区段; selected state 或 error state 的状态边界。
这些场景里,边界就是边界。它要清楚、稳定、可被状态覆盖。
但 Card、Popover、Dropdown、Modal 这类组件不太一样。它们很多时候不是在表达“这里有一条线”,而是在表达“这个 Surface 浮在背景之上”。
这就是 border + shadow 容易不舒服的地方。
box-shadow 的视觉逻辑是连续过渡:靠近元素的区域更暗,越往外越散。border 的视觉逻辑是固定几何线:1px、一个颜色、一圈闭合路径。
两者叠在一起时,浏览器可以正常绘制,但视觉结果看起来并不是很干净。尤其当 border 颜色和 shadow 的近边颜色很接近时,边缘会从“清楚的轮廓”变成“静态灰带”。
更好的做法:把边缘也放进阴影层
我觉得更好的写法是移除 border,然后在 box-shadow 的第一层加入一个 zero-blur 的 hairline(极细、几乎看不见的分隔线)。
这里真正重要的是第一层:
它没有位移,也没有模糊,只保留了扩散效果,因此会在组件外围形成一圈极淡的 1px 光晕。
这圈光晕第一眼看上去像边框,但本质上仍然属于阴影的一部分。
它和后续的接触阴影、环境阴影共用同一套颜色体系,只是透明度有所区别。
这样做的好处是,组件边缘不会突然冒出一条独立的灰线,而是从整个阴影系统中自然过渡出来,层次感会更统一,也更高级
这个很小的样式代码差异可以让人体会到界面存在明显差别。
shadow 的顺序也很关键
多个 box-shadow 的顺序不是装饰问题,而是绘制问题。
推荐把 hairline 放在第一层:
可以把这三层拆开理解:
edge ring :告诉眼睛 surface 的边界在哪里;contact shadow :处理元素贴近背景时的接触暗部;ambient shadow :表达整体视觉层级,也就是“浮起来”的感觉。
如果把这三个样式全部交给一个大的 shadow,边缘会不够明确。如果再额外加一个 border,又容易把边缘压成固定灰色。
所以比较干净的方案是:边缘仍然交给 shadow,只是单独给它一层由阴影实现的细边框。
用一张图来解释:
为什么这比 border 更适合做 Surface 的视觉层级
从组件的设计系统角度看,border 和 box-shadow 不是同一类“Token”。
border 更像 structure token。它定义组件的几何边界,也经常参与 state:default、hover、focus、error、disabled。
box-shadow 更像视觉层级 token。它定义 surface 和背景之间的空间关系。
当一个 card 的边缘只是为了让视觉层级更清楚时,用 border 会把两个语义混在一起:
这在简单页面里没问题,但在设计系统里会慢慢变麻烦。
比如你之后要调暗主题、调高对比度、换背景色、加 hover 时,都需要同时考虑 border 和 shadow 的关系。
而 hairline shadow 可以直接归入视觉层级:
这样维护起来更清楚,这条边缘属于视觉层级的一部分,而不是表单控件的边框,也不是用于表达状态的边框。
什么时候不要这么做
这个技巧不应该替代所有边框。
我会在这些场景继续使用 border 或 outline:
input、textarea、select 等表单控件; focus ring 和 keyboard navigation 相关状态; error、warning、selected 这类明确状态; table、divider、layout separator; 需要和布局尺寸强绑定的 1px 分割线。
原因很简单:这些场景里,线条本身就是语义的一部分。
而 hairline shadow 更适合这些组件:
Card; Popover; Dropdown menu; Modal; Command palette; Floating toolbar; Inspector panel。
这些组件的核心不是“我有一条边框”,而是“我是一个浮起来的 surface”。
参数怎么调
实际项目里可以从这个版本开始:
如果页面背景更浅,可以把 edge alpha 降到 0.06:
如果组件很小,比如 dropdown item 容器、compact popover,可以把大阴影收小:
如果是高密度工具界面,不建议把 alpha 拉太高。超过 0.12 之后,hairline 很容易重新变成“边框感”。
也可以针对高分屏使用半像素:
但这不是必须项。
很多生产项目里,1px + 更低 alpha 比 .5px + 更高 alpha 更稳定,也更容易跨浏览器保持一致。
如果还想要顶部更干净,可以加 inner highlight
有些白色 surface 的顶部会被阴影边缘压暗,看起来不够透。可以额外加一层非常轻的内侧高光。
这条 inset 不需要很明显,它只是为了给 Surface 顶部补一点微弱的亮边,让白色卡片在浅背景上更干净。如果一眼能看出“这里有高光”,反而过犹不及。
最后
这个技巧的重点不是“用 shadow 伪造 border”,我只是觉得应该把 UI 边界分成两类:
结构边界:用 border、outline、divider;层级边界:用 box-shadowstack 里的 hairline。
当组件在表达 state 或 layout,继续使用 border。
当组件在表达视觉层级, 就可以优先考虑 hairline shadow。
这条规则不复杂,但对设计系统很有帮助。它能让 Card、Popover、Modal 这类 Surface 的边缘更统一。
感谢大家看到这里,如果你喜欢我分享的内容,欢迎给我三连支持,你的支持是我更新下去的动力!
下次见,Bye 👋🏻

优网科技秉承"专业团队、品质服务" 的经营理念,诚信务实的服务了近万家客户,成为众多世界500强、集团和上市公司的长期合作伙伴!
优网科技成立于2001年,擅长网站建设、网站与各类业务系统深度整合,致力于提供完善的企业互联网解决方案。优网科技提供PC端网站建设(品牌展示型、官方门户型、营销商务型、电子商务型、信息门户型、微信小程序定制开发、移动端应用(手机站、APP开发)、微信定制开发(微信官网、微信商城、企业微信)等一系列互联网应用服务。
公安局备案号:
