Skip to content

Select 下拉选择框

从一组选项中选择单个值的下拉选择器,支持可选的搜索与表单参与。

适用场景:需要一个由 <r-option> 子元素构建的单值下拉选择器,可选支持搜索和原生表单参与——<r-select> 负责展开、过滤,以及 FormData 上报。

快速开始

基础用法

选项通过插槽里的 <r-option> 子元素提供。每个选项的 value 属性是它的值,文本内容是显示的标签。

MikeTomLucy
html
<r-select style="width: 120px; height: 40px" defaultValue="185">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

API 参考

Select 属性

属性类型默认值说明
labelstring''字段上方的静态标题——和 r-inputlabel 用法一致,方便和输入框对齐
valuestring''选中的值。设置它会更新收起状态下的显示文本;disabled 时忽略
defaultValuestring''初始选中的值,与选项的 value 匹配
disabledbooleanfalse是否禁用选择器
typestring''text 渲染成无边框、透明背景、不带箭头图标的触发器;否则带边框
openbooleanfalse下拉框是否展开。它就是状态本身,赋值即可开合
placementstring'bottom'下拉框展开的方向,可加对齐后缀:bottombottom-endtop-center
showSearchbooleanfalse是否显示按标签过滤选项的内联搜索框
getPopupContainerIdstring''下拉框挂载元素的 id(默认挂载到 document.body
dropdownclassstring''下拉面板的自定义 class 名
triggerstring'click'下拉框的触发方式:clickhover,或 click,hoverhover 在移动端无效)
requiredbooleanfalse表单提交前是否要求已选择
sheetstring''注入 shadow DOM 的自定义样式

注意defaultValueshowSearch 是响应式的——元素连接后再修改它们,也会在 attributeChangedCallback 里被重新处理(和 valuedisabledsheet 一样)。更新 defaultValue 会重新应用匹配的选中项;切换 showSearch 会装配或卸载内联搜索框。

Option 属性

选项通过 <r-option> 子元素提供。

属性类型默认值说明
valuestring''选项的值;被选中时作为 select 的值发出
disabledbooleanfalse标记该选项不可选——点击和键盘选择都会跳过它
sheetstring''注入选项 shadow DOM 的自定义样式

选项标签或值重复时会打印一条 console.warn

标题 label

字段上方的静态标题——始终可见,不会和相邻内容重叠。使用和 r-inputlabel 相同的 token 与布局,所以并排放置的带标题 select 和带标题 input 会对齐(同样的高度、同样的顶边)。

United StatesCanadaMexico
html
<r-select label="Country" defaultValue="185">
  <r-option value="185">United States</r-option>
  <r-option value="186">Canada</r-option>
  <r-option value="187">Mexico</r-option>
</r-select>

默认值 defaultValue

MikeTomLucy
html
<r-select style="width: 120px; height: 40px" defaultValue="185">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

禁用状态 disabled

MikeTomLucy
html
<r-select style="width: 120px; height: 40px" disabled defaultValue="185">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

文本类型 type

MikeTomLucy
html
<r-select style="width: 120px; height: 40px" type="text" defaultValue="185">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

下拉方向 placement

placement 只是一个偏好设置,不是保证:当触发器靠近视口边缘、偏好方向放不下时,下拉框会自动翻转到另一侧,并水平偏移以保持在屏幕内。这一行为只对默认的 body 级挂载生效——设置了 getPopupContainerId 时,请自行选一个适合容器的 placement

方向可以带一个对齐后缀——bottom-endtop-center 等,和 r-popover 是同一套写法。只写方向等同于 -start,即面板的起始边与触发器的起始边对齐。

只有当面板和触发器不一样宽时,后缀才会产生差别——默认情况下面板宽度就跟着触发器走。把面板撑宽(用 r-dropdown::part(dropdown),经 dropdownclass 定位到它,因为面板是挂到 <body> 上的、并不在 select 的 shadow root 里),对齐就会按真正绘制出来的宽度计算:

html
<style>
  r-dropdown.wide::part(dropdown) {
    min-width: 220px;
  }
</style>

<!-- 面板右边缘与触发器右边缘对齐 -->
<r-select placement="bottom-end" dropdownclass="wide" style="width: 80px">
  <r-option value="a">一个很长的选项文字</r-option>
</r-select>

另外,边界平移的优先级高于对齐:触发器离视口边缘足够近时,无论要求了哪种对齐,面板都会被推回可视区域内。

MikeTomLucy
html
<r-select style="width: 120px; height: 40px" defaultValue="185" placement="top">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

展开状态 open

open 就是下拉框的状态,像 <details open><dialog open> 一样反射为属性。没有任何地方再从面板的 display 反推状态——那个值比状态滞后一整段退场动画——所以属性、aria-expanded 和屏幕上看到的三者不会互相矛盾。

因此它既是驱动组件的正式方式,也是可以拿来写样式、写断言的东西:

html
<r-select id="picker" open>
  <r-option value="185">Mike</r-option>
</r-select>

<script>
  const picker = document.getElementById('picker');
  picker.open = true; // 或 picker.show()
  picker.open = false; // 或 picker.hide()
  picker.toggle();
</script>

<style>
  /* 面板展开时的触发器 */
  r-select[open]::part(selection) {
    border-color: var(--ran-color-primary);
  }
</style>

show()hide()toggle() 只是它的薄封装,用方法比赋值更顺手时可以用。

搜索功能 showSearch

MikeTomLucy
html
<r-select style="width: 120px; height: 40px" showSearch="true">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

触发方式 trigger

MikeTomLucy
html
<!-- 点击触发(默认) -->
<r-select trigger="click">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

<!-- 悬停触发(移动端无效) -->
<r-select trigger="hover">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

<!-- 点击和悬停都触发 -->
<r-select trigger="click,hover">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

挂载容器 getPopupContainerId

下拉框默认挂载到 document.body。传入另一个元素的 id,可以改为挂载到那个元素内。

html
<r-select getPopupContainerId="my-container">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

自定义下拉 class dropdownclass

html
<r-select dropdownclass="custom-dropdown">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

事件

change

选中某个选项时触发。event.detail{ value, label }value 是选中选项的值,label 是它显示的文本。选中初始的 defaultValue 不会触发 change

html
<r-select id="picker">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

<script>
  document.getElementById('picker').addEventListener('change', (e) => {
    console.log(e.detail.value, e.detail.label); // 例如 "186" "Tom"
  });
</script>

只有开启 showSearch 时才会触发,用户在搜索框输入时触发(节流过的)。event.detail{ value },也就是当前的搜索文本。组件内部也会按标签过滤可见选项。

html
<r-select showSearch="true" id="searchable">
  <r-option value="185">Mike</r-option>
  <r-option value="186">Tom</r-option>
  <r-option value="187">Lucy</r-option>
</r-select>

<script>
  document.getElementById('searchable').addEventListener('search', (e) => {
    console.log(e.detail.value);
  });
</script>

show / after-show / hide / after-hide

在面板开合的前后触发。showhide 表示意图,在过渡开始时发出;after-showafter-hide 在面板真正到位、动画结束后发出——需要"等面板确实消失了再做某事"时,听后面这一对。

这两对事件都不带 detail

html
<script>
  const picker = document.getElementById('picker');
  picker.addEventListener('show', () => console.log('正在展开'));
  picker.addEventListener('after-hide', () => console.log('已收起,动画也结束了'));
</script>

等待的是样式表里那个动画本身,而不是抄进脚本里的一个时长。所以在 prefers-reduced-motion 下——面板压根没有动画要播——after-hide 会紧接着 hide 发出,而不是干等一个固定延迟。

表单关联

r-select 是一个表单关联自定义元素(static formAssociated = true)。它通过 ElementInternals 上报选中的 value,因此只要是原生 <form> 的真实子孙元素,就会以该 select 的 namenew FormData(form) 收集。表单值在连接时就会从初始选中项中取值,之后随值变化保持同步。

重置:原生 form.reset() 会通过 formResetCallback() 恢复 defaultValue 对应的选中项(如果设置了的话),否则清空选中状态。

校验required 会让空选择通过 ElementInternals.setValidity() 变为无效状态,form.checkValidity()/form.reportValidity() 能感知到;disabled 的 select 永远不会阻塞校验。元素上暴露了和原生表单控件一致的 checkValidity()reportValidity()validityvalidationMessage

html
<form>
  <r-select name="country" required>
    <r-option value="us">United States</r-option>
    <r-option value="ca">Canada</r-option>
  </r-select>
  <button type="submit">提交</button>
</form>

插槽

插槽说明
(默认)接受用于定义可选项的 <r-option> 元素

CSS Parts

Part说明
selectselect 的根容器
selection触发器方框(边框、背景、布局)
icon下拉箭头图标
selection-item显示选中项标签的元素
search内联搜索输入框(showSearch 时可见)
label字段上方的静态标题(设置了 label 时存在)

最佳实践

  • 选项较多时:开启 showSearch,让用户能按标签过滤。
  • 触发方式trigger 要符合用户预期;hover 在移动端无效,记得保留 click
  • 挂载位置:在滚动或裁剪溢出内容的布局里,用 getPopupContainerId 控制下拉框挂载到哪里。
  • 自定义样式:用 dropdownclass 或暴露的 ::part() 名称来重新设计触发器和下拉框的样式。
  • 表单:给 select 加上 name,这样它的值才能被原生 <form> 里的 FormData 收集到。

Released under the MIT License.