Base UI React ToggleGroup 完整 API 参考:Props、类型、数据属性与源码实现解析
2026/9/15 19:21:54 网站建设 项目流程

Base UI React ToggleGroup 完整 API 参考:Props、类型、数据属性与源码实现解析

【免费下载链接】base-uiUnstyled UI components for building accessible web apps and design systems. From the creators of Radix, Floating UI, and Material UI.项目地址: https://gitcode.com/GitHub_Trending/ba/base-ui

本指南以 Base UI(本仓库packages/react/src/toggle-group包)的ToggleGroup组件为核心,系统梳理其官方 API 参考文档(types.md/react/components/toggle-group/types.md))中的全部 Props、Data Attributes、状态与事件类型,并结合源码实现、官方示例与测试用例进行纵深解析。读完本文,你将掌握ToggleGroup的受控/非受控用法、单选与多选切换、键盘焦点循环、与Toolbar的集成方式,以及render/className/style三种渲染定制手段,可直接用于构建文本格式工具栏、分段控件(Segmented Control)等无障碍交互组件。

一、组件定位:为一系列 toggle 按钮提供共享状态

ToggleGroup是一个无样式(unstyled)的 React 组件,官方定位是"Provides a shared state to a series of toggle buttons"——即把一组Toggle按钮(来自@base-ui/react/toggle)的按下状态收敛到一个共享的组状态中,类似"按钮组"或"分段控件"。与单选组(Radio Group)不同,它默认允许全部取消选中,且可以通过multiple属性切换为多选模式,因此非常适合文本格式化工具栏(粗体/斜体/下划线)、文本对齐方式选择等场景。

该定位可以直接在源码中得到印证:ToggleGroup.tsx 的 JSDoc 注释与文档页 page.mdx/react/components/toggle-group/page.mdx#L3) 的 Subtitle 完全一致。

二、快速上手:导入与 Anatomy

组件从@base-ui/react/toggle-group单独导出,官方 Anatomy 示例(见 page.mdx/react/components/toggle-group/page.mdx#L13-L21))展示其作为"单部件"使用的方式:

import { ToggleGroup } from '@base-ui/react/toggle-group'; <ToggleGroup />;

在实际使用中,ToggleGroup需要与Toggle组合。Toggle通过value属性向组内注册自己的值,而ToggleGroup用一个string[]数组(即"所有处于按下状态的 Toggle 的 value 集合")统一管理组内按下状态。完整的组合示例为:

import { ToggleGroup } from '@base-ui/react/toggle-group'; import { Toggle } from '@base-ui/react/toggle'; <ToggleGroup aria-label="Text alignment" defaultValue={['left']}> <Toggle value="left">左对齐</Toggle> <Toggle value="center">居中</Toggle> <Toggle value="right">右对齐</Toggle> </ToggleGroup>;

需要注意的是,ToggleGroup渲染的根元素默认是div,并带有role="group"(见 ToggleGroup.tsx),因此建议配合aria-label提供无障碍名称。测试用例 ToggleGroup.test.tsx 也验证了queryByRole('group')的语义。

三、ToggleGroup Props 完整参考

下表完整继承自官方 API 参考文档 types.md/react/components/toggle-group/types.md#L11-L24):

PropTypeDefaultDescription
defaultValuestring[]-The pressed state of the toggle group represented by an array of the values of all pressed toggle buttons. This is the uncontrolled counterpart ofvalue.
valuestring[]-The pressed state of the toggle group represented by an array of the values of all pressed toggle buttons. This is the controlled counterpart ofdefaultValue.
onValueChange((groupValue: string[], eventDetails: ToggleGroup.ChangeEventDetails) => void)-Callback fired when the pressed states of the toggle group changes.
loopFocusbooleantrueWhether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys.
multiplebooleanfalseWhenfalseonly one item in the group can be pressed. If any item in the group becomes pressed, the others will become unpressed. Whentruemultiple items can be pressed.
disabledbooleanfalseWhether the toggle group should ignore user interaction.
orientationOrientation'horizontal'-
classNamestring \| ((state: ToggleGroup.State) => string \| undefined)-CSS class applied to the element, or a function that returns a class based on the component's state.
styleReact.CSSProperties \| ((state: ToggleGroup.State) => React.CSSProperties \| undefined)-Style applied to the element, or a function that returns a style object based on the component's state.
renderReactElement \| ((props: HTMLProps, state: ToggleGroup.State) => ReactElement)-Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts aReactElementor a function that returns the element to render.

3.1 受控与非受控:value / defaultValue

  • 非受控:传入defaultValue时,组件内部自行维护按下状态,用户点击后状态自动更新。官方文档将其描述为value的非受控对应物。
  • 受控:传入value时,按下状态完全由外部组件持有,必须配合onValueChange回写,否则点击后 UI 不会变化。

源码层面,这一对状态由useControlledHook 统一管理(见 ToggleGroup.tsx):

const [groupValue, setValueState] = useControlled({ controlled: valueProp, default: defaultValue, name: 'ToggleGroup', state: 'value', });

defaultValue未传时,源码会回退到EMPTY_ARRAY(ToggleGroup.tsx),并借助isValueInitialized标记区分"未传值"与"显式传空数组"两种情形,用于后续对Toggle缺失value时的数据一致性告警。

3.2 multiple:单选与多选切换

multipleToggleGroup最核心的行为开关:

  • false(默认):组内同时只能有一个按钮处于按下状态;某个按钮按下时,其余按钮自动取消按下。点击已按下的按钮会将其取消(即允许全部不选,这是与 Radio Group 的关键差异)。
  • true:允许多个按钮同时按下,彼此独立,互不影响。

其内部实现逻辑在 ToggleGroup.tsx 的setGroupValue回调中:多选模式下对当前groupValue数组做push(按下)或splice(取消)操作;单选模式下则直接替换为[newValue][]

官方在 page.mdx/react/components/toggle-group/page.mdx#L25-L31) 中给出的 Multiple 示例为:

<ToggleGroup multiple defaultValue={['bold', 'italic']} aria-label="Text formatting options"> <Toggle value="bold" aria-label="Bold" /> <Toggle value="italic" aria-label="Italic" /> <Toggle value="underline" aria-label="Underline" /> </ToggleGroup>

3.3 loopFocus:键盘焦点循环

loopFocus默认true,控制使用方向键在组内移动焦点时,到达列表末尾是否回绕到第一个元素。该开关直接透传给内部基于 Composite 模式的CompositeRoot(见 ToggleGroup.tsx):

<CompositeRoot render={render} className={className} style={style} state={state} refs={[forwardedRef]} props={[defaultProps, elementProps]} loopFocus={loopFocus} enableHomeAndEndKeys orientation={orientation} />

从源码结构看,ToggleGroup复用了packages/react/src/internals/composite/root/CompositeRoot来提供方向键导航能力,并且固定启用了enableHomeAndEndKeys(Home/End 键跳转首尾)。同时方向键的移动方向由orientation决定。

3.4 disabled:整组禁用

disabled默认false,为true时整个ToggleGroup忽略用户交互。值得注意的是,源码中的禁用状态是合并结果(ToggleGroup.tsx):

const disabled = (toolbarContext?.disabled ?? false) || (toolbarGroupContext?.disabled ?? false) || disabledProp;

也就是说,当ToggleGroup被放置于被禁用的ToolbarToolbarGroup中时,即使自身未传disabled也会整体禁用。这一状态还会通过 Context 向下传给所有子Toggle

3.5 orientation:方向感知

orientation类型为Orientation'horizontal' | 'vertical'),默认'horizontal'。它同时影响两点:方向键焦点的移动方向(水平组用左右键、垂直组用上下键),以及根元素上的data-orientation数据属性取值。

3.6 onValueChange:变更回调

当组内按下状态变化时触发,签名如下:

onValueChange?: ( groupValue: Value[], eventDetails: ToggleGroup.ChangeEventDetails, ) => void;

第一个参数是变更后的完整按下值数组;第二个参数是事件详情对象(详见下文"事件详情类型")。从源码可以看出,回调在状态写入之前被调用,并且如果eventDetails.isCanceled为真,组件会跳过内部状态更新(ToggleGroup.tsx),因此你可以在回调中通过cancel()拦截状态变更。

四、渲染定制:render、className 与 style

ToggleGroup继承自 Base UI 的BaseUIComponentProps<'div', ToggleGroupState>(见 ToggleGroup.tsx),因此支持三种渲染定制方式:

  1. className:可以是普通字符串,也可以是接收ToggleGroup.State并返回字符串的函数,便于基于disabled/multiple/orientation状态做条件样式。官方 Tailwind 示例大量使用了这一点。
  2. style:普通样式对象或基于状态返回样式对象的函数,适合运行时动态计算样式。
  3. render:允许把默认的div替换为其他标签(如ul),或与另一个组件组合。可传入一个 ReactElement,或一个接收HTMLPropsToggleGroup.State并返回元素的函数。

三种方式会在非 Toolbar 场景下统一透传给CompositeRoot,在 Toolbar 场景下则通过useRenderElement处理(ToggleGroup.tsx),保证任何定制都能与内部状态(如data-*属性)保持一致。

五、Data Attributes:无样式 CSS 的状态锚点

由于组件无内置样式,官方推荐通过数据属性编写 CSS。下表完整继承自 types.md/react/components/toggle-group/types.md#L26-L32):

AttributeTypeDescription
data-orientation'horizontal' \| 'vertical'Indicates the orientation of the toggle group.
data-disabled-Present when the toggle group is disabled.
data-multiple-Present when the toggle group allows multiple buttons to be in the pressed state at the same time.

这三个属性的定义可在 ToggleGroupDataAttributes.ts 中直接找到,例如:

export const orientation = 'data-orientation'; export const disabled = 'data-disabled'; export const multiple = 'data-multiple';

官方 Tailwind 示例(demos/hero/tailwind/index.tsx/react/components/toggle-group/demos/hero/tailwind/index.tsx))正是基于data-pressed等属性实现按下态样式切换:

data-pressed:bg-neutral-950>type ToggleGroupState = { /** Whether the component should ignore user interaction. */ disabled: boolean; /** * When `false` only one item in the group can be pressed. If any item in * the group becomes pressed, the others will become unpressed. * When `true` multiple items can be pressed. * @default false */ multiple: boolean; /** The orientation of the toggle group. */ orientation: Orientation; };

它是classNamestylerender函数式用法中第二个参数的来源,也是 Base UI 状态驱动的样式方案的基础。

6.2 ToggleGroup.ChangeEventReason

type ToggleGroupChangeEventReason = 'none';

目前该组件所有变更事件的reason固定为'none'。在源码中它被定义为typeof REASONS.none(ToggleGroup.tsx),复用自packages/react/src/internals/reasons,表明事件原因机制已就位,未来若引入更多触发来源可直接扩展该联合类型。

6.3 ToggleGroup.ChangeEventDetails

onValueChange的第二参数字段完整定义如下(摘自 types.md/react/components/toggle-group/types.md#L62-L81)):

type ToggleGroupChangeEventDetails = { /** The reason for the event. */ reason: 'none'; /** The native event associated with the custom event. */ event: Event; /** Cancels Base UI from handling the event. */ cancel: () => void; /** Allows the event to propagate in cases where Base UI will stop the propagation. */ allowPropagation: () => void; /** Indicates whether the event has been canceled. */ isCanceled: boolean; /** Indicates whether the event is allowed to propagate. */ isPropagationAllowed: boolean; /** The element that triggered the event, if applicable. */ trigger: Element | undefined; };
  • cancel():调用后isCanceled变为true,组件会跳过内部状态更新(对应 ToggleGroup.tsx 的拦截逻辑),可用于实现"不允许取消最后一项"等业务规则。
  • allowPropagation():在 Base UI 会主动阻止传播的场景下,手动放行事件冒泡。
  • trigger:触发本次事件的 DOM 元素(若存在),便于定位用户点击的按钮。

6.4 外部类型 Orientation

Orientation是一个跨组件共享的外部类型(定义于packages/react/src/internals/types):

type Orientation = 'horizontal' | 'vertical';

它同时被ToggleGroupToolbarTabs等方向敏感组件复用,因此传值方式在各组件间保持一致。

6.5 Canonical Types 命名映射

官方文档提供了"规范命名(Canonical)↔ 别名(Alias)"的映射表,规则为:当命名空间ToggleGroup已被导入时优先用 Canonical 写法,否则使用 Alias

CanonicalAlias
ToggleGroup.StateToggleGroupState
ToggleGroup.PropsToggleGroupProps
ToggleGroup.ChangeEventReasonToggleGroupChangeEventReason
ToggleGroup.ChangeEventDetailsToggleGroupChangeEventDetails

在源码中这通过 TypeScript namespace 重新导出实现(ToggleGroup.tsx),两种写法类型完全等价。例如组件定义中的泛型签名ToggleGroup.Props<Value extends string>便采用了 Canonical 形式。

七、官方示例:单选与多选实战

7.1 单选:文本对齐工具条

官方 Hero 示例(demos/hero/tailwind/index.tsx/react/components/toggle-group/demos/hero/tailwind/index.tsx))演示了默认单选模式下的"文本对齐"工具栏,核心逻辑如下:

<ToggleGroup aria-label="Text alignment" defaultValue={['left']} className="flex gap-px p-px border border-neutral-950 dark:border-white" > <Toggle aria-label="Align left" value="left" className="...">…</Toggle> <Toggle aria-label="Align center" value="center" className="...">…</Toggle> <Toggle aria-label="Align right" value="right" className="...">…</Toggle> </ToggleGroup>

注意三个关键点:aria-label为整组提供可访问名称;defaultValue={['left']}设置初始选中项;由于未传multiple,点击任意按钮会自动取消其余按钮。

7.2 多选:文本格式工具栏

Multiple 示例(demos/multiple/tailwind/index.tsx/react/components/toggle-group/demos/multiple/tailwind/index.tsx))模拟常见的"加粗/斜体/下划线"格式工具栏:

<ToggleGroup multiple defaultValue={['bold', 'italic']} aria-label="Text formatting options" className="flex gap-px p-px border border-neutral-950 dark:border-white" > <Toggle aria-label="Bold" value="bold" className="...">…</Toggle> <Toggle aria-label="Italic" value="italic" className="...">…</Toggle> <Toggle aria-label="Underline" value="underline" className="...">…</Toggle> </ToggleGroup>

multiple开启后,bolditalic可以同时保持按下,这是文本编辑器中"组合样式"的典型交互。仓库同时提供了 CSS Modules 版本(demos/multiple/css-modules/)与 Tailwind 版本供对照学习。

八、源码实现解析:状态、Context 与 Toolbar 集成

深入阅读 ToggleGroup.tsx 的实现,可以归纳出四条关键实现路径:

  1. 状态管理:受控/非受控统一交给useControlled@base-ui/utils/useControlled);更新回调经useStableCallback包装,保证在依赖变化后仍能拿到最新groupValue,避免闭包过期。
  2. Context 下发:组件通过ToggleGroupContext.Provider下发{ value, setGroupValue, disabled, isValueInitialized }(ToggleGroupContext.ts),子Toggle借此读取组状态、上报自己的按下事件,并在缺少value且组已初始化值时发出告警。
  3. Toolbar 集成:组件会主动探测上层的ToolbarRootContextToolbarGroupContext(ToggleGroup.tsx)。当处于 Toolbar 内部时,焦点管理与禁用态继承自 Toolbar,渲染走useRenderElement;否则自行渲染CompositeRoot提供方向键与焦点循环。这解释了为什么在 Toolbar 内嵌套ToggleGroup时无需重复声明键盘导航能力。
  4. 无障碍语义:默认渲染div并附加role="group",配合每个Togglearia-pressed状态(由子组件维护),整组对屏幕阅读器呈现为"可切换按钮组"。

九、测试验证:行为契约

ToggleGroup.test.tsx(共 605 行)为上述 API 行为提供了可执行的契约证明,关键断言包括:

  • 无障碍角色:渲染后可通过role="group"查询到根元素(#L18-L22)。
  • 非受控按下状态:点击value="one"的按钮后,该按钮aria-pressed变为true且带data-pressed,另一按钮仍为false#L24-L53)。
  • defaultValue 初始选中defaultValue={['two']}时第二个按钮初始即按下,点击其他按钮后互斥切换(#L55-L74)。
  • Toggle 缺失 value 告警:组内Toggle未传value且组已定义value/defaultValue时,控制台会输出精确的错误提示(#L98-L113)。
  • 受控模式:通过setProps重设value可同步更新 UI(#L116-L120起)。

这些测试既验证了 Props 的行为语义,也确认了aria-presseddata-pressed等属性由Toggle侧负责输出,而ToggleGroup仅负责整组状态与焦点管理——二者职责划分清晰,值得在阅读源码时对照体会。

十、小结

ToggleGroup是 Base UI 中"分组状态 + 键盘导航 + 无障碍语义"三者结合的典型组件:value/defaultValue/onValueChange完成状态受控闭环,multiple决定单选或多选,loopFocusorientation接管键盘体验,disabled支持与Toolbar联动的整组禁用,className/style/render提供无样式场景下的全部渲染定制入口,而data-orientationdata-disableddata-multiple三个数据属性则让样式层能够纯粹基于状态编写。配合官方示例、源码与测试,你可以在此基础上快速落地自己的分段控件或格式工具栏。

【免费下载链接】base-uiUnstyled UI components for building accessible web apps and design systems. From the creators of Radix, Floating UI, and Material UI.项目地址: https://gitcode.com/GitHub_Trending/ba/base-ui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询