写过自定义 checkbox 的人大概都踩过这个坑——每个控件都要找一堆 hack

写过自定义 checkbox 的人大概都踩过这个坑——每个控件都是一场 hack:隐藏原生 input、用 label 模拟、用 ::before 画对勾、还要小心 keyboard focus 和 screen reader。最后引了一堆样式补丁,代码比业务逻辑还长。

CSS 其实从 2022 年开始就给了你一把钥匙,只是大部分人不知道它在哪。

这把钥匙叫 appearance: none

它到底能干什么

appearance: none 的作用很简单:告诉浏览器「别给这个元素加任何原生样式」。按钮就是按钮,checkbox 就是 checkbox,语义还在,但渲染全归你管。

它能处理的控件覆盖了表单里最常见的几类:

  • input[type="checkbox"]input[type="radio"] — 自定义选择控件
  • select — 自定义下拉框(去掉浏览器默认箭头)
  • buttoninput[type="submit"] — 统一按钮样式
  • input[type="range"] — 自定义滑块
  • input[type="file"] — 自定义文件上传按钮

三行代码,自定义 checkbox 长这样

这是最典型的用法。原生 checkbox 那个小方块在每个浏览器里长得都不一样——圆角、边框粗细、选中颜色,没有一个统一标准。appearance: none 让你从零开始画:

/* 先去掉原生样式 */
input[type="checkbox"] {
  appearance: none;
  width: 20px;
  height: 20px;
  border: 2px solid #d1d5db;
  border-radius: 4px;
  cursor: pointer;
  /* 这里开始画你自己的样式 */
}

/* 选中状态 */
input[type="checkbox"]:checked {
  background-color: #2563eb;
  border-color: #2563eb;
}

/* 用 ::after 画对勾 */
input[type="checkbox"]:checked::after {
  content: ;
  display: block;
  width: 5px;
  height: 10px;
  border: 2px solid white;
  border-top: none;
  border-left: none;
  transform: rotate(45deg) translate(2px, -2px);
}

这个写法比传统的「隐藏原生 input,用 label 加背景图」干净得多——没有图片依赖,没有 opacity hack,CSS 全搞定。

select 也能这样自定义

select 的下拉箭头在 Chrome、Safari、Firefox 里长得完全不同,有时候还会被系统主题覆盖。用 appearance: none 去掉它,然后自己加箭头:

select {
  appearance: none;
  padding: 10px 36px 10px 12px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  background-color: white;
  background-image: url("data:image/svg+xml,%3Csvg xmlns=http://www.w3.org/2000/svg viewBox=0 0 20 20 fill=%236b7280%3E%3Cpath d=M5 8l5 5 5-5/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 20px;
  cursor: pointer;
}

三件事:去掉默认箭头、加 SVG 小箭头、用 padding 留出箭头位置。就这样,跨浏览器一致了。

radio 按钮同理

input[type="radio"] {
  appearance: none;
  width: 20px;
  height: 20px;
  border: 2px solid #d1d5db;
  border-radius: 50%;
  cursor: pointer;
}

input[type="radio"]:checked {
  border-color: #2563eb;
  background-color: white;
}

/* 内部圆点 */
input[type="radio"]:checked::after {
  content: ;
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: #2563eb;
  margin: 3px;
}

两个必须注意的坑

坑一:无障碍别丢了。 隐藏原生 input 的时候,别用 display: nonevisibility: hidden,那会让屏幕阅读器读不到它。正确做法是用 position: absolute; opacity: 0; 把它移出文档流但保留语义。checkbox 本身的 :checked 状态在加了 appearance: none 之后依然有效,键盘 focus 也还在——只要你不手动把它 display: none,无障碍就不会断。

坑二:老 Safari 还要前缀。 iOS Safari 15.3 及之前、macOS Safari 15.4 及之前还需要 -webkit-appearance: none。生产环境建议这样写:

input[type="checkbox"] {
  -webkit-appearance: none;
  appearance: none;
  /* 样式 */
}

写在前面的 appearance: none 是给现代浏览器看的,-webkit- 前缀兜底层兼容。

全球覆盖率 96%,现在用完全没问题

根据 caniuse 数据,appearance 属性(包含 none 值)全球覆盖率 96%+,Chrome 84+、Firefox 80+、Safari 15.4+、Edge 84+ 全部支持。2022 年进入 Baseline 之后,已经不需要任何 polyfill。

三步下一步

  1. 先试 checkbox。 在你的项目里找一个简单的表单,把 appearance: none 用上去,感受一下「CSS 全权控制」是什么体验。不用任何 JS,不用任何图片依赖。
  2. 把 select 也做了。 加一个自定义箭头 SVG,跨浏览器统一下拉框外观,这是 appearance: none 最实用的场景之一。
  3. 检查无障碍。 用 Tab 键验证键盘可聚焦,用屏幕阅读器验证读得出来。自定义样式不能以牺牲无障碍为代价——这是底线。

用了 appearance: none 之后,你可能会意识到之前靠多少 hack 才勉强实现了「自定义表单控件」。这把钥匙一直在,只是知道的人不多。

评论区

0 条评论

登录后可评论。