跳到主要内容

Segmented

  • 组件说明:Segmented 是一个互斥值选择器,用于在少量选项间切换(如视图模式切换、筛选维度切换)。与 Tabs 不同,它不绑定内容面板——需要同时切换可见内容时应使用 Tabs
  • 交互特征:始终保持一项选中;点击已选中项不产生任何效果,也不会触发 onChange。同时支持受控与非受控两种模式。
  • 实现约定:直接基于 @radix-ui/react-toggle-grouptype="single")封装,不经过 ui/toggle-group——其默认的 toggleVariants 视觉与 Segmented 冲突,复用前需先给整个 toggle 系列补齐 unstyledVisual 支持,不在本次迭代范围内。
  • Figma 规范

基础用法

非受控——未传 defaultValue 时默认选中第一项。

结果
Loading...
实时编辑器
<Segmented options={['Day', 'Week', 'Month']} defaultValue="Week" />

内容形态

3 种内容形态,对应 Figma Style 变体轴。

纯文字

结果
Loading...
实时编辑器
<Segmented options={['List', 'Grid', 'Table']} defaultValue="List" />

图标 + 文字

结果
Loading...
实时编辑器
<Segmented
  options={[
    { value: 'search', label: 'Search', icon: <Search size={16} /> },
    { value: 'add', label: 'Add', icon: <Plus size={16} /> },
  ]}
  defaultValue="search"
/>

纯图标

纯图标选项(无 label)必须提供 iconaria-label——这条约束在类型层面强制,避免纯图标按钮缺少可访问名。

结果
Loading...
实时编辑器
<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"
/>

受控用法

结果
Loading...
实时编辑器
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 让根节点撑满父容器宽度,各选项等分空间。

结果
Loading...
实时编辑器
<div style={{ maxWidth: 360 }}>
  <Segmented options={['Overview', 'Analytics', 'Settings']} defaultValue="Overview" block />
</div>

禁用

可以整体禁用,也可以单独禁用某一项。

整体禁用

结果
Loading...
实时编辑器
<Segmented options={['List', 'Grid', 'Table']} defaultValue="List" disabled />

单项禁用

结果
Loading...
实时编辑器
<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#3D3D3D400
未选中 / HoverGrays/Gray-2#D6D6D6Labels/Secondary#3D3D3D400
选中(默认与 Hover 完全一致)Foregrounds/White#FFFFFFForegrounds/Black#000000600
禁用 / 未选中(Figma 未覆盖,实现补齐)Labels/Disabled#A3A3A3400
禁用 / 选中(Figma 未覆盖,实现补齐)Foregrounds/White#FFFFFFLabels/Disabled#A3A3A3600

禁用 + 选中态保留选中药丸的白底与阴影,但文字色降级为 Labels/Disabled——对齐 antd Segmented 的禁用表现(「这是当前选中项,但不可更改」),而非停留在纯黑字。

选中态使用 Foregrounds/White / Foregrounds/Black,而非 Backgrounds/Primary / Labels/PrimaryForegrounds 系列在明暗主题下均为纯白/纯黑,设计意图是选中药丸不随主题翻转。选中态还带有 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,均达标。

几何尺寸

属性备注
容器 padding2px硬编码 fallback——design-tokens 中无对应 token
容器圆角7px硬编码 fallback——design-tokens 只有 Radius_5 / Radius_12 / Radius_20 / Rounded,没有 7px
容器 gapSpacing_4
Item 圆角Radius_5
Item padding-y2px
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

属性类型默认值说明
optionsreadonly (string | SegmentedOption)[]-选项列表;裸字符串是 { value: s, label: s } 的简写
valuestring-受控选中值
defaultValuestring-非受控默认值;缺省取第一项
blockbooleanfalse撑满父容器宽度,选项等分空间
disabledbooleanfalse整体禁用
onChange(value: string) => void-选中值变化回调;不会以空值触发
aria-labelstring-无可见标题时的可访问名
classNamestring-根节点自定义类名
itemClassNamestring-统一施加到每个选项的类名

其余 div 属性(iddata-*onKeyDown 等)会透传到根节点。

SegmentedOption

裸字符串(如 'a')是 { value: 'a', label: 'a' } 的简写。

字段类型默认值说明
valuestring-唯一标识值
labelReactNode-文字内容;省略时 iconaria-label 变为必填
iconReactNode-图标内容;无 label 时需与 aria-label 一起提供
disabledbooleanfalse禁用该项
classNamestring-该项自定义类名
aria-labelstring-可访问名;无 label(纯图标)时必填

与 antd 的差异

  • size(尺寸变体)、shape(形状变体)、vertical(竖向排列)均未实现——Figma 目前没有对应的变体。