我想向您介绍一个新的实验性表单控件,名为 <selectmenu>
。 我们将深入探讨它,包括它比传统 <select>
元素更容易样式化 的方式。 不过首先,让我们了解一下为什么首先需要像 <selectmenu>
这样的东西,因为它仍在不断发展和开发中。
问问任何 Web 开发人员他们认为当今 Web 平台缺少什么,他们很有可能在他们的清单中列出对表单控件进行样式设置的能力。 事实上,表单样式被评为 2020 年 CSS 状态调查中缺少的十大事物之一。 然后,它由 Greg Whitworth 进一步调查,他 展示了,<select>
是 Web 开发人员在使用 CSS 进行样式设置时遇到的问题最多的控件。
虽然对 <select>
的按钮部分(在弹出窗口关闭时在页面中看到的东西)的外观进行样式设置相对容易,但几乎不可能对选项(在弹出窗口打开时看到的东西)进行样式设置,更不用说在弹出窗口中添加更多内容了。

<select>
元素的默认 UI因此,设计系统和组件库一直在推出自己的选择,这些选择从头开始使用自定义 HTML 标记、CSS 以及大量的 JavaScript 构建而成,以便拥有能够与其他组件很好地集成的选择。
不幸的是,以正确的可访问性语义、键盘支持和弹出窗口定位正确地做到这一点并不容易。 多年来,Web 开发人员投入了大量时间,试图一遍又一遍地解决相同的问题,并且有很多不可访问的选择。
是时候拥有一个可样式化的内置 <select>
,这样我们就不必再写这些代码了!
Open UI 计划

Open UI 是一个由开发人员、设计师和浏览器实现者组成的团队,他们着手解决这个问题,并在解决这个问题的同时,也解决了其他缺少的控件。
Open UI 的目的是最终使 Web 开发人员能够对内置 UI 控件进行样式设置和扩展(这包括 <select>,但也包括下拉菜单、复选框、单选按钮等)。 为了实现这一点,他们会生成有关如何在 Web 平台上实现这些控件以及它们应该解决的可访问性要求的规范。
该项目仍处于起步阶段,但进展迅速,正如我们将在下面看到的那样,令人兴奋的事情正在发生。
您可以加入该团队,参与会议、研究和规范工作。
<selectmenu>
控件
根据 Open UI 的 <select>
提案,新的 <selectmenu>
控件的实现已在 Chromium 中启动! 这项工作由 Microsoft Edge 团队与 Google Chrome 团队合作完成。 它甚至已经在基于 Chromium 的浏览器中提供,方法是在 about:flags
页面中启用“实验性 Web 平台功能”标志。
<selectmenu>
是一个新的内置控件,它提供选项选择用户体验,就像 <select>
一样,带有显示所选值标签的按钮、单击该按钮时出现的弹出窗口以及显示的选项列表。
为什么是新名称?
为什么不直接替换现有的 <select>
控件? “selectmenu” 这个名称最初是一个工作名称,但它似乎一直沿用至今,还没有人想出更好的名称。
更重要的是,现有的 <select>
控件在 Web 上已经使用了很长时间。 因此,它可能永远无法以任何重大方式更改,否则会导致重大兼容性问题。
因此,计划(记住,这都是非常实验性的)是让 <selectmenu>
成为一个新的控件,独立于 <select>
。
立即尝试
这还没有准备好用于生产环境,但如果您像我一样兴奋地使用它,以下是如何操作
- 打开基于 Chromium 的浏览器的 Canary 版本(Chrome、Edge)。
- 在
about:flags
页面中切换“实验性 Web 平台功能”标志,然后重新启动。 - 在网页中将任何
<select>
替换为<selectmenu>
!
就是这样! 默认情况下它不会做太多,但正如我们将在后面看到的那样,您将能够使用此标签名称更改对控件进行广泛的样式设置和扩展。
我们喜欢反馈!
在我们深入了解如何使用该控件之前,如果您确实使用了它,Open UI 团队和在 Chromium 中负责实施该控件的人员非常乐意听取您的任何反馈。
通过成为早期测试人员,您可以积极帮助他们为所有人改进该控件。 因此,如果您在设计控件时遇到错误或限制,请通过 在 Open UI GitHub 存储库中创建一个问题 发送您的反馈!
现在,让我们谈谈该控件是如何工作的。
<selectmenu>
控件的解剖结构
因为 selectmenu 的各个部分可以进行样式设置,所以首先了解它的内部解剖结构很重要。

<selectmenu>
是包含按钮和列表框的根元素。<button>
是触发列表框可见性的元素。<selected-value>
是显示当前选择选项值的元素(可选)。 请注意,这部分不一定必须放在<button>
部分中。<listbox>
是包含<option>
和<optgroup>
的包装器。<optgroup>
将选项分组在一起,并带有可选的标签。<option>
表示用户可以选择的潜在值。 可以有一个或多个。
默认行为
<selectmenu>
控件的默认行为模仿 <select>
控件的行为。 您可以像使用原生 <select>
一样使用它,使用以下最小标记。
<selectmenu>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectmenu>
这样做时,默认的 <button>
、<selected-value>
和 <listbox
>将为您创建。
对控件的各个部分进行样式设置
这就是事情变得有趣的地方! 对控件进行样式设置以匹配您的要求的一种方法是使用 CSS ::part()
伪元素来选择要设置样式的控件解剖结构中的不同部分。
考虑以下使用 ::part()
对按钮和列表框部分进行样式设置的示例
<style>
.my-select-menu::part(button) {
color: white;
background-color: #f00;
padding: 5px;
border-radius: 5px;
}
.my-select-menu::part(listbox) {
padding: 10px;
margin-top: 5px;
border: 1px solid red;
border-radius: 5px;
}
</style>
<selectmenu class="my-select-menu">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectmenu>
上面的示例会产生以下样式

::part()
可用于对控件的 <button>
、<selected-value>
和 <listbox>
部分进行样式设置。
使用您自己的标记
如果以上内容不足以满足您的需求,您可以通过提供自己的标记来替换默认标记,并扩展或重新排序各个部分,从而更深入地自定义控件。
一个 <selectmenu>
具有命名的 插槽,可以引用它们来替换默认部分。例如,要将默认按钮替换为自己的按钮,您可以执行以下操作:
<style>
.my-custom-select [slot='button'] {
display: flex;
align-content: center;
}
.my-custom-select button {
padding: 5px;
border: none;
background: #f06;
border-radius: 5px 0 0 5px;
color: white;
font-weight: bold;
}
.my-custom-select .label {
padding: 5px;
border: 1px solid #f06;
border-radius: 0 5px 5px 0;
}
</style>
<selectmenu class="my-custom-select">
<div slot="button">
<button behavior="button">Open</button>
<span class="label">Choose an option</span>
</div>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectmenu>
外部 <div>
上的 slot="button"
属性告诉 <selectmenu>
将其默认按钮替换为 <div>
的内容。
内部 <button>
上的 behavior="button"
属性告诉浏览器,此元素是我们想要用作新按钮的元素。浏览器会自动将所有点击和键盘处理行为应用于此元素,以及相应的可访问性语义。
以上代码段将生成以下样式:

请注意,slot
和 behavior
属性也可以在同一个元素上使用。
您可以通过类似的方式替换默认的列表框部分:
<style>
.my-custom-select [popup] {
width: 300px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 10px;
padding: 10px;
box-shadow: none;
margin: 10px 0;
border: 1px solid;
background: #f7f7f7;
}
</style>
<selectmenu class="my-custom-select">
<div slot="listbox">
<div popup behavior="listbox">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
<option>Option 4</option>
<option>Option 5</option>
</div>
</div>
</selectmenu>
有趣的是,这里使用的 <div popup>
也正在由 Open UI 提议 并目前在 Chromium 中实现。
具有 behavior="listbox"
的元素必须是 <div popup>
。应用 behavior="listbox"
会告诉浏览器在点击 <selectmenu>
按钮时打开此元素,用户可以使用鼠标、箭头键和触摸在其中选择 <option>
。
以上代码段将生成以下样式:

扩展标记
除了可以用自己的标记替换默认部分外,如上所述,您还可以通过添加新元素来扩展控件的标记。这对于用额外信息增强列表框或按钮,或添加新功能非常有用。
考虑以下示例:
<style>
.my-custom-select [slot='button'] {
display: flex;
align-items: center;
gap: 1rem;
}
.my-custom-select button {
border: none;
margin: 0;
padding: 0;
width: 2rem;
height: 2rem;
border-radius: 50%;
display: grid;
place-content: center;
}
.my-custom-select button::before {
content: '\25BC';
}
.my-custom-select [popup] {
padding: 0;
}
.my-custom-select .section {
padding: 1rem 0 0;
background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%);
}
.my-custom-select h3 {
margin: 0 0 1rem 0;
text-align: center;
color: white;
}
.my-custom-select option {
text-align: center;
padding: 0.5rem;
}
</style>
<selectmenu class="my-custom-select">
<div slot="button">
<span class="label">Choose a plant</span>
<span behavior="selected-value" slot="selected-value"></span>
<button behavior="button"></button>
</div>
<div slot="listbox">
<div popup behavior="listbox">
<div class="section">
<h3>Flowers</h3>
<option>Rose</option>
<option>Lily</option>
<option>Orchid</option>
<option>Tulip</option>
</div>
<div class="section">
<h3>Trees</h3>
<option>Weeping willow</option>
<option>Dragon tree</option>
<option>Giant sequoia</option>
</div>
</div>
</div>
</selectmenu>
在这里,我们使用自定义标记来包装选项列表并创建我们自己的内容,如下所示:

替换整个影子 DOM
最后,如果以上内容还不够,您还可以通过调用 attachShadow()
来完全替换控件的默认影子 DOM。例如,上一节中的演示可以修改如下:
<selectmenu id="my-custom-select"></selectmenu>
<script>
const myCustomSelect = document.querySelector('#my-custom-select')
const shadow = myCustomSelect.attachShadow({ mode: 'closed' })
shadow.innerHTML = `
<style>
.button-container {
display: flex;
align-items: center;
gap: 1rem;
}
button {
border: none;
margin: 0;
padding: 0;
width: 2rem;
height: 2rem;
border-radius: 50%;
display: grid;
place-content: center;
}
button::before {
content: '\\0025BC';
}
[popup] {
padding: 0;
}
.section {
padding: 1rem 0 0;
background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%);
}
h3 {
margin: 0 0 1rem 0;
text-align: center;
color: white;
}
option {
text-align: center;
padding: 0.5rem;
}
option:hover {
background-color: lightgrey;
}
</style>
<div class="button-container">
<span class="label">Choose a plant</span>
<span behavior="selected-value" slot="selected-value"></span>
<button behavior="button"></button>
</div>
<div popup behavior="listbox">
<div class="section">
<h3>Flowers</h3>
<option>Rose</option>
<option>Lily</option>
<option>Orchid</option>
<option>Tulip</option>
</div>
<div class="section">
<h3>Trees</h3>
<option>Weeping willow</option>
<option>Dragon tree</option>
<option>Giant sequoia</option>
</div>
</div>
`
</script>
以这种方式编写,<selectmenu>
的自定义标记完全封装在其影子 DOM 中。因此,<selectmenu>
可以放到任何页面中,而不必担心周围内容的样式干扰。
结束语
如我们所见,新的实验性 <selectmenu>
控件在样式化甚至扩展传统 <select>
方面提供了很大的灵活性。而且它以所有正确的方式做到了这一点,因为它内置于浏览器中,在浏览器中,可访问性和视口感知定位会为您处理。
Open UI 有关于 <selectmenu>
的 更多文档,如果您想查看更多展示如何使用 <selectmenu>
的代码,这里有一些演示。
再说一次,这是一项正在进行的工作,肯定会随着 Open UI 小组收到的反馈而改变。
我迫不及待地想看到规范开始出现在 HTML 和 CSS 标准机构中,以及实现变得更加稳定,以及看到其他浏览器引擎对此产生兴趣。您可以帮助实现这一点!测试控件、报告问题或 参与其中 都是推动这项工作取得进展的好方法。
感谢您在文章中提到了可访问性,但我认为它没有深入探讨这个新元素的挑战。
为了让任何新元素都能被所有用户正确地使用,它也必须映射到平台可访问性 API。我可以创建一个
<gumball>
,但如果我的操作系统没有本地等效项,也没有为我的屏幕阅读器、键盘、语音控制、开关等进行映射,那么它就毫无意义。例如,如果
<option>
的映射方式与今天相同,那么它们实际上只能包含文本。并且它们的兄弟节点(listbox
角色的子节点)也只能是<option>
。这是一个巨大的 未解决的挑战,它会限制实际使用,这意味着开发人员可以继续使用不可访问的替代方案。虽然使用其他方法(例如 CSS 背景图像)可以减轻我们在 可访问性名称计算 方面遇到的问题,但这同时也意味着对 CSS 生成的内容的替代文本 的支持必须成为依赖项——它必须首先在所有浏览器中得到支持。祝您在 Safari 中一切顺利。
一些示例还 需要更好的键盘支持,才能评估其其他功能。我认为,除非至少能正确处理键盘访问,否则不应该发布任何演示。不这样做会降低人们对模式的信任。
目前,如果您只需要对触发器进行一些字体、大小和颜色控制,那么您可以使用本机
<select>
做到这一点。其他示例模式是各种各样的快速披露小部件、新颖的组合框以及 实验性模式,用户难以理解。
所有这些都是锤炼任何新元素过程的一部分。但我想确保可访问性方面不会被再次视为事后诸葛亮。
感谢 Adrian 提出这些问题。我也想确保可访问性不被视为事后诸葛亮,我认为它并没有被视为事后诸葛亮。我参加的关于
<selectmenu>
的 Open UI 会议绝对将可访问性作为重要讨论主题之一。此外,我们离生产就绪还很远。因此,还有时间,我完全预期这个新元素会随着时间的推移而改变,这将是这种讨论的结果。
然而,看到这方面取得的进展非常令人兴奋,因为这已经是一个长期存在的问题。我发布演示的初衷是为了让这项工作更具可见性,从而提高人们对该项目的意识,并可能促进人们参与进来。
我很乐意在它们上投入更多时间,并确保它们具有更好的键盘支持。它们最初只是作为一种锻炼
`‘ 的自定义样式功能的方式,但确实是我添加了一些具有更复杂自定义标记的演示,而这些演示确实引发了一些问题。我认为这些问题值得提出。
是的!自从我开始开发以来,我一直呼吁这样做。我和设计师以及项目经理进行过很多次对话,他们说我们要么无法实现这个设计,要么需要一些额外的 JS 来包含这个东西,而这个东西通常不是语义化的 HTML,或者可能会增加相当多的膨胀,这让我头疼。
一方面,我很高兴终于有人做了这件事。另一方面,我很讨厌,通过引入一个新元素,似乎除了使用 JavaScript polyfill 之外,没有其他优雅的降级方法。这意味着,几年后,我才能依靠它无处不在,以便在无需在我的代码中同时使用
<select>
和<selectmenu>
的情况下使用它,从而使代码膨胀。如果没有它,它将完全无法被禁用 JavaScript 的用户使用。这不像现在的<select>
元素有一个list
属性允许这两个组件共享<option>
。我喜欢这个想法,不喜欢这个实现。
新的语法过于复杂,新的
<selectmenu>
元素没有必要。为什么不简单地扩展现有的<select>
元素,允许对<option>
元素应用 CSS 样式?这将与现有的<select>
元素向后兼容。我相信,这是该项目参与者最初考虑过的选择之一。他们认为,新实现将引入的潜在兼容性问题非常重要,不能简单地替换现有
(抱歉,我上一条评论中包含了 HTML 代码,而我忘记转义了,让我重新发布。)
我相信,这是最初参与此项目的人员考虑的选择之一。但是,新实现带来的潜在兼容性问题被认为过于重要,无法仅仅用新的 `<select>` 控件来替换现有的控件,因此目前选择了新名称。
话虽如此,但目前还处于非常早期的阶段,一切皆有可能。
我只能建议您在 OpenUI 仓库中提出此问题:https://github.com/openui/open-ui/issues/
我可以在 selectmenu 的选项中放入一个 selectmenu,以构建一个不使用 JavaScript 的级联下拉菜单导航栏吗?
我不知道当前的实现是否会阻止您这样做,但我认为不会。但我不会建议您为此使用 selectmenu。该控件用于让用户从列表中选择一个值,在其中嵌入其他输入会带来有趣的可访问性挑战。
但是,请查看此演示:https://sulfuric-purring-meteorite.glitch.me/selectmenu-edit-menu.html
它使用弹出元素创建子菜单。
我们很久以前就放弃了原生 HTML 选择,而是使用 Semantic UI/Fomantic UI 的下拉菜单、Select2 等。
这绝对是一个巨大的进步!
我也一直在问“为什么他们不能给我们一个新的 `<select>` 标签。现在已经是 2022 年了。”
我很高兴看到它开始被使用。
这是一个简单的函数,用于查看浏览器是否支持新的 `<selectmenu>` 标签
const selectMenuExists = () => window.HTMLSelectMenuElement != null;
非常令人兴奋的东西!我有点担心和之间的命名问题。我认为,我们的标准越依赖历史偶然性,随着时间的推移,它们就越发具有个性化和不可教性。希望能够达成不同的妥协。
由于这似乎是在幕后由 Web 组件构成的,那么现在是否可以作为 polyfill 实现它?
看起来真的很酷很有用 :)
我有一点担心,那就是“实验性功能”标志。
我无法在产品中使用这个非常好的功能,因为我无法强迫我的客户更改此标志来使用我的应用程序。
您是否能估计一下我可以在不更改标志的情况下使用它吗?