深色模式上线了。设计评审看着很漂亮——黑底、白字,每个按钮上都是品牌的荧光绿点缀。然后QA在晚上11点打开注册表单测试出生日期字段,点了一下日期输入框,一个名片大小的白色矩形在整片黑色屏幕中间亮了起来,像有人把显示器忘在了暗房里。
同样的事也发生在下拉选择框上,还有滚动条——它仍然是默认的浅灰色,搁在黑色侧边栏上像一道粉笔印。这些地方没人写过CSS,因为它们本来就不是你的CSS。它们是浏览器对表单控件的原生渲染,而你没碰过这些控件,它们还停留在浅色模式,因为你从没告诉浏览器,这个页面对配色有自己的主张。
![]()
直觉做法为什么修不好
最直接的反应是冲着控件本身去:给日期输入框和下拉框设上深色背景、浅色文字、深色边框。这确实能把框体重新上色——输入框自己的背景和边框会顺利接上深色主题。
但点进日期字段,浏览器画在上层的日历弹窗——真正的日期网格、月份标题、那几对小箭头——仍然是纯白的弹窗。这些内部界面不是你能选中的DOM节点。它是操作系统或浏览器的原生外观,由一条你的样式表没有钩子的内部渲染路径绘制出来。下拉框展开后的列表也是同样的原生外观,尽管收起状态的框体不是。
于是人们开始升级方案:隐藏原生日历,自己写一个日期选择组件;为下拉框引入一个替换库。深色模式的代价变成了一整套重建的控件,而它本来只需要一条声明。
浏览器其实自带两套调色板
浏览器为自己的原生界面——表单控件、原生弹窗、滚动条——内置了两套调色板,一套浅色,一套深色。默认情况下,每个页面拿到的都是浅色那套,跟你页面自己的背景无关,因为浏览器不知道你的页面支持深色渲染,除非你明确告诉它。
这才是缺的那块拼图。问题不是"我怎么给日期选择器重新上色",而是"我怎么告诉浏览器该用它内置的哪套调色板"。
答案是在根元素上写一行 color-scheme: light dark;。设置一次就够。它告诉浏览器:这个页面两套配色都支持,请挑一套匹配用户操作系统偏好的,并据此绘制你自己的原生界面。
日历弹窗、下拉列表、滚动条的轨道和滑块,在用户系统设为深色时都会以深色调色板渲染。不需要一行JavaScript,不需要重建任何控件,因为浏览器本来就有这个能力,它只是需要被告知你的页面已经选择加入。
接下来容易踩的坑
color-scheme 重绘的是浏览器自己的渲染,不是你的组件。如果你用 appearance: none 手写了一个自定义复选框,或者用 div 搭了一个下拉框,那跟 color-scheme 没有关系——那些仍然完全归你的CSS管,和以前一样。
这个属性补上的是你没重建的那部分控件的缺口,它不是整个页面的深色模式总开关。
基础版本落地之后,还有两处细节值得知道:
- meta标签能压住闪烁。CSS只有在被解析和下载之后才会生效,而浏览器在那之前就已经开始排版页面了。把等价的声明写成 head 里的 meta 标签,浏览器在样式表到达之前就知道了,这能消掉浅色滚动条闪现一帧、然后才被深色替换的问题。
- only 用来强制表态。一个刻意始终保持深色的应用——代码编辑器、终端模拟器——根本不想跟随用户的系统偏好。color-scheme: only dark 会把原生界面钉死在深色上,不管 prefers-color-scheme 报告什么。这和单独写 color-scheme: dark 不同,后者仍然会被强制配色模式之类的东西覆盖。只有当设计确实不提供浅色版本时才用它,别当默认值。
下次深色模式的bug以"日历弹窗还是白的"形式出现时,先检查 color-scheme,再去考虑重建一个日期选择组件。你自己的应用里,还有哪个原生控件是你一直在手工覆盖,而这一行其实早就覆盖了的?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.