表单
Select
从一个列表里选一项,从头到尾都由我们绘制。
什么时候用它
示例
default
The list is ours, not the platform’s — open it.
说明
从一个列表里选一项,从上到下都是自己的样式。
选项列表是我们自己的——用的是和别处同一套 token,所以它不会一打开就换掉字体、间距和选中色。这正是它取代原生控件成为默认值的全部理由:一个最常用的表单控件一被点击就不再属于这套系统的设计系统,那不是设计系统,那是一份只管闭合状态的样式表。
键盘约定是 Radix 的,也就是平台的:首字母跳转能用,方向键能走,Home 和 End 到两端,Escape 不选中直接关闭。那本来是留在原生方案上唯一真正站得住的论据,现在它被回答了。
超过大约十几个选项就换 Combobox——一个没法筛的列表,比一个能打字进去的更难用。在平台选择器确实更好的地方——手机上,或者一个必须在没有 JavaScript 时也能活的表单——换 NativeSelect。
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| children必填 | ReactNode | 若干 `SelectItem`,也可以用带 `SelectLabel` 的 `SelectGroup` 包起来。 | |
| label必填 | string | 给控件命名。必填——触发器上显示的是值,而值不是名字。 | |
| className | string | — | |
| contentClassName | string | 给选项**面板**用的类名,不是给触发器的。 `className` 设的是触发器,那是常见情况。这个是给不常见的那种:一个放在有边界的表面里的 select——默认 18rem 的列表会盖住读者正要为之做选择的那个东西,比如年份选择器盖住它自己的日历。 | |
| disabled | boolean | — | |
| invalid | boolean | 把静止态的边框画成 `--danger`,并反映到 `aria-invalid` 上。 | |
| placeholder | string | 'Select…' | — |
同时接受 ComponentProps<typeof SelectPrimitive.Root> 里的全部属性,它们会直接透传给底层元素,不再逐条列出。
组成部分
Composed at the call site rather than configured through props, so a layout this component did not anticipate is still expressible.
SelectItem
一个选项。对勾标出选中的那个;填充标出高亮的那个。
同时接受 ComponentProps<typeof SelectPrimitive.Item> 里的全部属性,它们会直接透传给底层元素,不再逐条列出。
SelectLabel
一组选项的等宽小标题。
必须放在 SelectGroup 里面——否则 Radix 会抛错,因为一个不属于任何分组的标题是在给虚无做标题,而辅助技术会把它当成一个选项念出来。
这个组件没有自己的属性。
SelectSeparator
分组之间的细线分隔。
这个组件没有自己的属性。
再导出
SelectRoot = SelectPrimitive.RootRadix Select 的根节点、分组和标签,带类型的原样透传。
SelectGroup = SelectPrimitive.Group键盘操作
| 按键 | 作用 |
|---|---|
| EnterSpace↓ | 展开列表。 |
| ↑↓ | 在选项之间移动。 |
| a–z | 首字母跳转——跳到下一个以该字母开头的选项。 |
| HomeEnd | 跳到第一个或最后一个选项。 |
| Escape | 不选中直接关闭。 |
无障碍
- 在有边界的框里——设备预览、内嵌控制台——用 `<OverlayContainer container={el}>` 包住这棵子树。面板会渲染进那个元素,按它的边界翻转,而不是按视口;框上设的 `dir` 和 `data-density` 也就跟着生效了。
- 选项列表是我们自己的,所以它不会在打开的一瞬间换掉字体、间距和选中色——那正是原生 select 会做的事。
- 键盘行为仍是平台的那一套:首字母跳转、方向键、Home 和 End、Escape 关闭且不选。
- label 必填。触发器上显示的是值,值不是名字。