前端知库

可定制 select(appearance: base-select、::picker(select))解决什么问题?

原生 select 的下拉面板样式由操作系统决定,几乎改不了,所以很多项目只能自己写下拉组件。给 select 和 ::picker(select) 设置 appearance: base-select 后,可以完全自定义按钮和下拉面板,选项里还能放图标,同时保留原生 select 的键盘操作、表单提交和无障碍语义。不支持的浏览器会退回普通 select。

参考答案

简答

原生 <select> 展开后的下拉面板由操作系统绘制,CSS 基本改不了它的样式,选项里也只能放纯文字。设计稿一要求带图标、圆角、自定义箭头,很多团队就只好用 div 自己写一个下拉组件,键盘操作和无障碍都得重新实现。

可定制 select 让原生 <select> 也能完全改样式。在 select 和代表下拉面板的 ::picker(select) 上都设置 appearance: base-select,浏览器就去掉系统样式,只保留一套最基础的样式,剩下的都交给 CSS。

设置以后,<select> 的第一个子元素可以是 <button>,用来替换关闭状态下的按钮,里面放 <selectedcontent> 显示当前选中项。<option> 里可以放图片、图标这类非交互内容。::picker-icon 改下拉箭头,::checkmark 改选中标记。下拉面板本身是一个 popover,打开时进入顶层,并且已经自动用锚点定位跟按钮关联好了。

要注意支持现状。按 MDN 兼容数据,Chrome 135 和 Safari 27 起支持,Firefox 只能通过实验开关开启,所以还不是 Baseline。好在它是渐进增强,不支持的浏览器会忽略这些写法,显示普通的 <select>,功能照常可用。

怎样开启

开启只需要一段 CSS,两个地方都要写:

CSS
select,
::picker(select) {
  appearance: base-select;
}

select 设置后,关闭状态的按钮可以自定义;::picker(select) 设置后,展开的下拉面板可以自定义。可以只给 select 开启、面板保持系统样式,但不能只开启面板而不开启 select。

选项里放图标

下面做一个带图标的选择框:

HTML
<select name="pet">
  <button>
    <selectedcontent></selectedcontent>
  </button>
  <option value="">请选择宠物</option>
  <option value="cat">
    <span class="icon" aria-hidden="true">🐱</span>
    <span>猫</span>
  </option>
  <option value="dog">
    <span class="icon" aria-hidden="true">🐶</span>
    <span>狗</span>
  </option>
</select>

以前 <select> 里不能放 <button>,<option> 里也只能放文字,这两点在可定制 select 里都放开了。

<selectedcontent> 会把当前选中的 <option> 的内容克隆一份显示在按钮里,所以选了“猫”,按钮上也会带着猫的图标。这个按钮默认是 inert 的,即使里面放了链接或按钮,整体也只当成一个按钮来交互。

那表单提交的值是什么?和原来一样是 value。没有写 value 时,浏览器取选中项的 textContent 去掉首尾空白作为值。

箭头、选中标记和面板位置

CSS
select::picker-icon {
  transition: rotate 0.4s;
}

select:open::picker-icon {
  rotate: 180deg;
}

option::checkmark {
  content: '✓';
}

::picker(select) {
  top: calc(anchor(bottom) + 4px);
}

::picker-icon 是按钮里的下拉箭头,配合 :open 伪类可以在展开时转过来。::checkmark 是选中项前面的勾。这两个伪元素不在无障碍树里,content 写的文字读屏软件不会读出来,所以不要把重要信息放进去。

下拉面板是一个 popover,打开时进入顶层,不会被父元素的 overflow 裁掉。它和按钮之间已经有隐式的锚点关联,直接用 anchor() 就能调整位置,不用再写 anchor-name。浏览器默认样式还带了备选位置,面板快超出视口时会自动换个方向。锚点定位的细节见《CSS Anchor Positioning 适合解决哪些浮层布局问题?》。

不支持的浏览器会怎样

不支持的浏览器会完全忽略 <button><selectedcontent></selectedcontent></button> 这段结构,<option> 里的非文字内容也会被去掉,只留下文字。最后得到的是一个普通的原生 <select>,能正常选择和提交。

所以它可以直接用在生产环境里,只要保证每个选项的文字本身就能说清楚意思。需要只在支持的浏览器里生效的样式,可以放进 @supports (appearance: base-select) 里。MDN 还提醒,有些 JavaScript 框架会拦截这些新元素,或者在服务端渲染时导致水合失败,用之前要在自己的框架里试一下。

常见追问

和自己用 div 写的下拉组件比,好在哪里?

它仍然是原生 <select>,键盘操作、表单提交和读屏语义都由浏览器提供,不用自己实现。自己写的组件要补齐这些,见《可访问的 Combobox 需要哪些键盘和 ARIA 行为?》。

下拉面板能做打开动画吗?

可以。面板是 popover,可以和其他 popover 一样用 @starting-style 做进入动画,见《@starting-style 和离散属性过渡解决了什么问题?》。