Segmented
- 组件说明:Segmented 是一个互斥值选择器,用于在少量选项间切换(如视图模式切换、筛选维度切换)。与
Tabs不同,它不绑定内容面板——需要同时切换可见内容时应使用Tabs。 - 交互特征:始终保持一项选中;点击已选中项不产生任何效果,也不会触发
onChange。同时支持受控与非受控两种模式。 - 实现约定:直接基于
@radix-ui/react-toggle-group(type="single")封装,不经过ui/toggle-group——其默认的toggleVariants视觉与 Segmented 冲突,复用前需先给整个 toggle 系列补齐unstyledVisual支持,不在本次迭代范围内。 - Figma 规范
基础用法
非受控——未传 defaultValue 时默认选中第一项。
<Segmented options={['Day', 'Week', 'Month']} defaultValue="Week" />
内容形态
3 种内容形态,对应 Figma Style 变体轴。
纯文字
<Segmented options={['List', 'Grid', 'Table']} defaultValue="List" />
图标 + 文字
<Segmented options={[ { value: 'search', label: 'Search', icon: <Search size={16} /> }, { value: 'add', label: 'Add', icon: <Plus size={16} /> }, ]} defaultValue="search" />
纯图标
纯图标选项(无 label)必须提供 icon 与 aria-label——这条约束在类型层面强制,避免纯图标按钮缺少可访问名。
<Segmented aria-label="Icon only demo" options={[ { value: 'search', icon: <Search size={16} />, 'aria-label': 'Search' }, { value: 'add', icon: <Plus size={16} />, 'aria-label': 'Add' }, ]} defaultValue="search" />
受控用法
const ControlledSegmented = () => { const [value, setValue] = useState('day') return ( <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}> <Segmented options={[ { value: 'day', label: 'Day' }, { value: 'week', label: 'Week' }, { value: 'month', label: 'Month' }, ]} value={value} onChange={setValue} /> <span style={{ fontSize: 12, color: '#999' }}>Selected: {value}</span> </div> ) } render(<ControlledSegmented />)
Block 撑满
block 让根节点撑满父容器宽度,各选项等分空间。
<div style={{ maxWidth: 360 }}> <Segmented options={['Overview', 'Analytics', 'Settings']} defaultValue="Overview" block /> </div>
禁用
可以整体禁用,也可以单独禁用某一项。
整体禁用
<Segmented options={['List', 'Grid', 'Table']} defaultValue="List" disabled />
单项禁用
<Segmented options={[ { value: 'list', label: 'List' }, { value: 'grid', label: 'Grid', disabled: true }, { value: 'table', label: 'Table' }, ]} defaultValue="list" />
无障碍
根节点渲染 role="radiogroup",每个选项渲染 role="radio" + aria-checked,对齐 W3C 对 segmented control 推荐的 ARIA 模式(Radix ToggleGroup 默认给根节点 role="group",与其 type="single" 模式下给选项赋予的 radio role 语义不匹配,Segmented 显式覆盖)。纯图标选项在类型层面强制要求 aria-label,确保每个选项始终具备可访问名。
设计 Token
颜色
| 状态 | 背景 | 文字色 | 字重 |
|---|---|---|---|
| 未选中 / 默认 | — | Labels/Secondary#3D3D3D | 400 |
| 未选中 / Hover | Grays/Gray-2#D6D6D6 | Labels/Secondary#3D3D3D | 400 |
| 选中(默认与 Hover 完全一致) | Foregrounds/White#FFFFFF | Foregrounds/Black#000000 | 600 |
| 禁用 / 未选中(Figma 未覆盖,实现补齐) | — | Labels/Disabled#A3A3A3 | 400 |
| 禁用 / 选中(Figma 未覆盖,实现补齐) | Foregrounds/White#FFFFFF | Labels/Disabled#A3A3A3 | 600 |
禁用 + 选中态保留选中药丸的白底与阴影,但文字色降级为 Labels/Disabled——对齐 antd Segmented 的禁用表现(「这是当前选中项,但不可更改」),而非停留在纯黑字。
选中态使用 Foregrounds/White / Foregrounds/Black,而非 Backgrounds/Primary / Labels/Primary:Foregrounds 系列在明暗主题下均为纯白/纯黑,设计意图是选中药丸不随主题翻转。选中态还带有 0 0 32px var(--Effects-Shadow-Default) 的阴影。
未选中态文字使用 Labels/Secondary(light #3D3D3D / dark #D6D6D6),而不用视觉上更浅的 Labels/Tertiary(#6B6B6B):Tertiary 实测压在容器背景(Grays/Gray-1,#EBEBEB)上对比度仅 4.47:1,压在 hover 背景(Grays/Gray-2,#D6D6D6)上更低至 3.67:1,均低于 WCAG 2.1 AA 要求的 4.5:1。Labels/Secondary 实测未选中默认对比度 light 9.11:1 / dark 8.69:1,未选中 hover 对比度明暗均为 7.47:1,均达标。
几何尺寸
| 属性 | 值 | 备注 |
|---|---|---|
| 容器 padding | 2px | 硬编码 fallback——design-tokens 中无对应 token |
| 容器圆角 | 7px | 硬编码 fallback——design-tokens 只有 Radius_5 / Radius_12 / Radius_20 / Rounded,没有 7px |
| 容器 gap | Spacing_4 | |
| Item 圆角 | Radius_5 | |
| Item padding-y | 2px | |
| Item padding-x(纯文字) | 左右各 Spacing_12 | |
| Item padding-x(图标+文字) | 左 Spacing_8 / 右 Spacing_12 | |
| Item padding-x(纯图标) | 左右各 Spacing_8 | |
| 字号 / 行高 | 13px / 18px(Font-Size-Footnote / Line-Height-Footnote) | |
| 图标尺寸 | 20×20px | 颜色继承 currentColor;wrapper 内部居中(inline-flex items-center justify-center),小于 20×20px 的图标也能与 Item、同排文字保持垂直居中 |
Item 的变体矩阵(Selected × Hover × Style)位于 Figma 组件集节点 26805:24,与上方容器节点同属一个文件(5ssRkvUdqpsRwwW59ooQCp)。
Props
Segmented
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
options | readonly (string | SegmentedOption)[] | - | 选项列表;裸字符串是 { value: s, label: s } 的简写 |
value | string | - | 受控选中值 |
defaultValue | string | - | 非受控默认值;缺省取第一项 |
block | boolean | false | 撑满父容器宽度,选项等分空间 |
disabled | boolean | false | 整体禁用 |
onChange | (value: string) => void | - | 选中值变化回调;不会以空值触发 |
aria-label | string | - | 无可见标题时的可访问名 |
className | string | - | 根节点自定义类名 |
itemClassName | string | - | 统一施加到每个选项的类名 |
其余 div 属性(id、data-*、onKeyDown 等)会透传到根节点。
SegmentedOption
裸字符串(如 'a')是 { value: 'a', label: 'a' } 的简写。
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | - | 唯一标识值 |
label | ReactNode | - | 文字内容;省略时 icon 与 aria-label 变为必填 |
icon | ReactNode | - | 图标内容;无 label 时需与 aria-label 一起提供 |
disabled | boolean | false | 禁用该项 |
className | string | - | 该项自定义类名 |
aria-label | string | - | 可访问名;无 label(纯图标)时必填 |
与 antd 的差异
size(尺寸变体)、shape(形状变体)、vertical(竖向排列)均未实现——Figma 目前没有对应的变体。