Web 功能:
可自定义的 <select>
过去,原生 <select> 元素很难设置样式,并且只能包含纯文本选项。appearance: base-select 属性提供了一种仅使用 CSS 的声明式方法,可让 <select> 元素进入可自定义状态。这样一来,开发者便可在 <option> 元素中添加丰富的 HTML 内容(例如图片、SVG 和复杂布局),同时保留原生键盘无障碍功能和表单集成。使用此模式,将繁重的自定义选择组件替换为标准的原生元素。
如何实现
如需使用 Customizable Select API 实现富媒体选择工具,请执行以下操作:
- 选择启用基本样式:使用
::picker(select)伪元素将appearance: base-select同时应用于<select>元素及其内部选择器。这会更改浏览器针对<select>内内容的 HTML 解析器。 - 定义按钮内容:在
<select>中使用标准<button>和<selectedcontent>元素,定义选择器关闭时显示的内容。<selectedcontent>元素会自动镜像所选选项的内容。如果您想在按钮中显示当前选择的富媒体内容,则必须执行此操作。 - 在选项中使用富媒体内容:您现在可以在
<option>标记中放置图片、SVG 和其他 HTML 标记。在此 API 推出之前,浏览器会从<option>标记中剥离标记,并仅呈现纯文本。 - 设置弹出式窗口的样式:通过定位
::picker(select)伪元素来设置下拉选项列表的样式。它在顶层渲染,这意味着您无需与z-index争夺层级。选项使用 Anchor Positioning API 原生定位。
示例代码:富角色选择器
<label for="role-picker">Select your role</label>
<select class="custom-select" name="role" id="role-picker">
<button>
<selectedcontent></selectedcontent> <!-- Mirrors the selected option's content automatically so you do not need JS to update the button -->
</button>
<!-- Define concise aria-label values on options whose mirrored rich content would otherwise read awkwardly as a concatenated string -->
<option value="frontend" aria-label="Frontend Developer">
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
</svg>
<div class="option-text">
<span class="option-title">Frontend Developer</span>
<span class="option-desc">React, Vue, CSS</span>
</div>
</option>
<option value="backend" aria-label="Backend Developer">
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="2" y="12" width="20" height="14" rx="2" ry="2"></rect>
</svg>
<div class="option-text">
<span class="option-title">Backend Developer</span>
<span class="option-desc">Node.js, Python</span>
</div>
</option>
</select>
select.custom-select,
select.custom-select::picker(select) {
appearance: base-select; /* MUST opt-in both the select and its picker to enable the customizable state, otherwise browser standard rendering applies */
}
select.custom-select {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 12px;
background-color: #1e293b;
border: 1px solid #334155;
border-radius: 8px;
color: #f1f5f9;
cursor: pointer;
}
select.custom-select::picker(select) {
background-color: #0f172a;
border: 1px solid #334155;
border-radius: 8px;
padding: 8px;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
width: anchor-size(width); /* Uses Anchor Positioning API to keep the dropdown precisely aligned to the button trigger width */
}
select.custom-select option {
display: flex;
align-items: center;
gap: 12px;
padding: 10px;
border-radius: 4px;
cursor: pointer;
}
select.custom-select option:hover {
background-color: #1e293b;
}
/* Remove standard OS checkmark for base-select */
select.custom-select option::before {
display: none;
}
/* MANDATORY: Provide multiple visual indicators (e.g., prominent background color and bold title font) to communicate the checked state cleanly */
select.custom-select option:checked {
background-color: #3b82f6;
color: #ffffff;
}
select.custom-select option:checked .option-title {
font-weight: 700;
}
策略性实施和最佳实践
- 建议:当您需要复杂的选项布局(图标、说明、图片)时,请使用
appearance: base-select,以前只有使用繁重的 JavaScript 界面框架才能实现。 - 如果您发现性能滞后,请不要使用临时元素;浏览器会以原生方式处理原生键盘焦点。
- 务必考虑顶层渲染。选择器会在顶层呈现,这意味着它会替换页面内容的相对
z-index。 - 务必通过设置
.custom-select selectedcontent .option-desc { display: none; }的样式,在按钮状态中隐藏次要细节(例如说明),以免占用过多空间。 - 测试布局行为。设置
appearance: base-select会移除默认浏览器行为,即根据最长选项宽度调整选择框大小。您可能需要设置固定宽度或使用 flex/grid 限制来防止布局偏移。 - 确保您的
<select>具有name属性和关联的<label>。这样可以确保即使采用自定义界面,屏幕阅读器也能访问该组件,并且该组件能与标准表单提交功能正常配合使用。 - 在选项中使用标准
value属性,以确保表单提交功能与之前完全相同。
后备策略
可自定义 <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.
}
});