自定义选择器布局

Web 功能: 可自定义的 <select>

CSS 属性 appearance: base-select 能够像常规 HTML 元素一样设置 <select> 元素及其选择器(通过 ::picker(select))的样式。 结合使用 Grid 或 Flexbox 等现代 CSS 布局技术,您可以摆脱传统的堆叠式垂直列表,在不使用自定义 JS 的情况下打造丰富的视觉体验。 例如,您可以使用 display: grid 将选项设置为二维卡片网格。

这非常适合颜色选择器、表情符号选择器或商品款式/规格,在这些场景中,丰富的视觉菜单比列表更有效。

如何实现

1. 启用基本样式

将 appearance: base-select 同时应用于 <select> 元素及其内部选择器伪元素 select::picker(select):

select.grid-picker {
  &, &::picker(select) {
    appearance: base-select;
  }
}

2. 设置选择器容器的样式

定位 select:open::picker(select) 并应用 display: grid(或 display: flex)。 像设置任何容器一样定义列和间隙。

select.grid-picker:open::picker(select) {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
}

重要提示::open是必需的。如果您设置 select::picker(select) 的样式,即使选择器处于关闭状态,选择器也会显示!

3. 设置选项样式

定位到 <option> 元素,将其样式设置为网格项或卡片。 您可以在其中添加图片、SVG 或复杂布局。

/* Style options as grid cards */
.grid-picker option {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* padding, border-radius, background, etc. */
}

4. 设置所选选项的样式

如需为所选 option 设置样式,请使用 option:checked。例如:

.grid-picker option:checked {
  position: relative;
  border: 2px solid var(--primary-color);
  background-color: oklab(from var(--primary-color) l a b / 8%);
}

与任何界面控件一样,请勿仅通过颜色传达状态,而应使用多种视觉指示标志(例如颜色、边框粗细、对勾标记)来传达所选选项。

5. (可选)为所选选项的对勾标记设置样式

默认情况下,浏览器会将对勾标记呈现为每个 <option> 的第一个子项。

它的行为类似于带有 content: "✓" / "" 的 ::before,在作者提供的 ::before(如果有)之前呈现。

如果未选择该选项,则会通过 visibility: hidden 隐藏对勾标记。

您可以使用 ::checkmark 伪元素为其设置样式,也可以将其完全隐藏(如果您想仅通过样式传达选择状态或使用自定义指示器)。

例如,如需将其放置在选项的右上角,您可以执行以下操作:

select.grid-picker {
  > option::checkmark {
    /* Hide the checkmark for unselected options */
    display: none;
  }

  > option:checked {
    position: relative;

    &::checkmark {
      position: absolute;
      inset-block-start: -.4em;
      inset-inline-end: -.5em;
      display: flex;
      align-items: center;
      justify-content: center;
      box-sizing: border-box;
      min-inline-size: 1.6em;
      aspect-ratio: 1;
      padding-right: 0.12em;
      padding-left: 0.05em;
      border-radius: 50%;
      background: var(--primary-color);
      color: white;
      font-size: 70%;
      font-weight: bold;
    }
  }
}

提示:在对勾标记周围绘制容器(例如圆形)时,请将其稍微向上移动一点(就像我们在这里使用 padding-bottom 和 padding-left 所做的那样),以便根据其形状进行视觉居中。 请勿为此使用 padding-inline-start 或 padding-block-end,因为它们会根据书写模式而变化,而对勾标记的形状不会变化。

6. (可选)自定义所选选项的显示

默认情况下,<select> 将显示所选选项的纯文本版本。 如需为所选值显示富媒体内容,请在 <button> 中使用 <selectedcontent>,和/或以不同方式显示所选选项(例如,隐藏某些详细信息)。

请勿在不使用 <button> 的情况下使用 <selectedcontent>,因为 <selectedcontent> 会像任何其他内容元素一样,直接成为选择器的一部分。

最佳做法

  • 测试布局行为。设置 appearance: base-select 会移除默认浏览器行为,即根据最长选项宽度调整选择框大小。您可能需要设置固定宽度或使用 flex/grid 限制来防止布局偏移。
  • 确保您的 <select> 具有 name 属性和关联的 <label>。这样可以确保即使采用自定义界面,屏幕阅读器也能访问该组件,并且该组件能与标准表单提交功能正常配合使用。
  • 在选项中使用标准 value 属性,以确保表单提交功能与之前完全相同。

无障碍

原生 <select> 仅处理线性向上/向下箭头导航,因此在打开的选择器中,向左/向右键不起作用,而向上/向下键仍会按顺序浏览各项,无论行和列如何。

如需实现遵循网格的空间导航,您可以拦截 keydown,并在用户使用箭头键时将焦点路由到所按方向上最近的选项(例如使用 getBoundingClientRect())。 如需确定合适的下一个/上一个项,请使用对实际几何图形进行操作的 DOM 方法(例如 getBoundingClientRect()),而不是解析 grid-template-columns 或 grid-column 等 CSS 字符串,因为对于复杂的网格、子网格、RTL 和其他布局场景,这些字符串可能很脆弱且容易出错。 也就是说,不要进行不必要的过度设计;请使用您知道的关于布局的不变量来简化此逻辑。

注意:这仅涉及键盘导航。<select> 及其 ::picker() 仍具有其默认角色(menu 或 listbox),这些角色无法被替换,也不允许包含具有 role="grid" 的元素。 在选择器中嵌套一个带有 role="grid" 的封装容器在实践中可行,但并不有效,并且可能会让 AT 用户感到意外。 在某些情况下,使用 <optgroup> 对选项进行分组可能会在不使用 role="grid" 的情况下产生良好的屏幕阅读器用户体验。 否则,最终可能需要改用自定义解决方案,即使用 popover API、锚点定位和 role="grid",为屏幕阅读器用户提供理想的用户体验。

后备策略

可自定义 <select> 的回退和浏览器支持

对于尚不支持 appearance: base-select 的浏览器,<select> 元素会优雅降级为标准操作系统下拉菜单。

  • 忽略非文本内容:旧版浏览器会剥离 <option> 标记内的 HTML 标记(例如 <svg> 或 <div>),仅呈现文本节点。确保 <option> 的文本内容本身清晰易读且有意义。
  • HTML 结构处理:标准解析器可能会忽略 <select> 内的 <button> 和 <selectedcontent> 标记,或将其视为无效。如果您将标准文本视为可读的后备内容,则渐进增强功能不需要任何繁重的 JavaScript polyfill。
document.addEventListener("DOMContentLoaded", () => {
  // Check if browser supports base-select value
  if (!CSS.supports("appearance", "base-select")) {
    // Custom select overrides are not supported natively.
  }
});