
Base UI Toolbar 组件 API 全解析分组控件容器、roving focus 键盘导航与组合渲染【免费下载链接】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 官方 API 参考文档 docs/src/app/(docs)/react/components/toolbar/types.md/react/components/toolbar/types.md) 为骨架系统讲解Toolbar组件的全部部件Root、Button、Input、Group、Separator、Link、Props、Data Attributes 与类型系统并结合仓库内 packages/react/src/toolbar 的源码实现与测试用例深入剖析其 roving focus 键盘导航、disabled 状态级联、render组合渲染等底层原理。读完本文你将能够熟练配置 Base UI Toolbar理解每个配置项对最终 DOM 与可访问性ARIA行为的具体影响并能在自己的设计系统中用它快速搭建对齐工具栏、富文本编辑栏等场景。组件概览Toolbar 是什么Toolbar.Root是用于分组一组控件的容器例如按钮、切换组或菜单默认渲染一个div元素见 ToolbarRoot.tsx 源码。它是典型的无样式unstyled复合组件自身不携带任何视觉样式只负责管理子项之间的焦点关系与语义结构。从 ToolbarRoot.tsx 的默认 props 可以看到Root 在 DOM 层会自动补充两个关键可访问性属性roletoolbar向辅助技术屏幕阅读器声明这是一个工具栏容器aria-orientation根据orientationprop 输出horizontal | vertical。该组件通过 index.ts 以命名空间形式导出import { Toolbar } from base-ui/react/toolbar之后通过Toolbar.Root、Toolbar.Button、Toolbar.Input等子部件访问。API 组成六个部件一览Toolbar由以下部件组成每个部件都遵循 Base UI 统一的Part.State/Part.Props命名空间导出模式部件渲染元素核心职责Toolbar.Rootdiv容器管理 roving focus 与全局 disabled/orientationToolbar.Buttonbutton工具栏按钮可作为其他组件如 Select.Trigger的触发器Toolbar.Inputinput原生输入框接入工具栏键盘导航Toolbar.Groupdiv对若干工具栏项进行分组支持整组禁用Toolbar.Separatordiv对屏幕阅读器可访问的分隔线Toolbar.Linka链接项始终可聚焦、不可被禁用下文逐一展开每个部件的 Props 与 Data Attributes并给出源码级依据。Root核心 Props 与 Data AttributesRoot PropsPropTypeDefaultDescriptionloopFocusbooleantrue如果为true键盘导航到达工具栏一端时会环绕wrap到另一端继续聚焦。disabledboolean-禁用整个工具栏。orientationToolbar.Root.Orientationhorizontal工具栏的方向。classNamestring \| ((state: Toolbar.Root.State) string \| undefined)-应用于元素的 CSS 类也可以传入一个基于组件状态返回类的函数。styleReact.CSSProperties \| ((state: Toolbar.Root.State) React.CSSProperties \| undefined)-应用于元素的样式也可以传入一个基于组件状态返回样式对象的函数。renderReactElement \| ((props: HTMLProps, state: Toolbar.Root.State) ReactElement)-允许你将组件的 HTML 元素替换为其他标签或与另一个组件组合。接受一个ReactElement或一个返回待渲染元素的函数。Root Data AttributesAttributeTypeDescriptiondata-orientationhorizontal \| vertical指示工具栏的方向。data-disabled-当工具栏被禁用时出现。Root 的源码实现CompositeRoot 与 disabledIndices在 ToolbarRoot.tsx 中Root 把方向与禁用状态放入ToolbarRootContext供所有子部件通过useToolbarRootContext()读取随后将导航职责委托给内部复合组件CompositeRootconst disabledIndices React.useMemo(() { const output: number[] []; for (const itemMetadata of itemMap.values()) { // 只有已禁用且 focusableWhenDisabled 为 false的项 // 才会从 roving focus 中移除。 if (itemMetadata.disabled !itemMetadata.focusableWhenDisabled) { output.push(itemMetadata.index); } } return output; }, [itemMap]);这段逻辑非常关键并非所有 disabled 项都会从键盘导航中剔除。CompositeRoot会收到disabledIndices、loopFocus、orientation与onMapChange由它统一实现 roving tabindex 与方向键导航内部实现见 packages/react/src/internals/composite。子项通过注册CompositeMetadata上报disabled与focusableWhenDisabled元信息Root 据此计算哪些索引应从导航中移除——这正是禁用但可聚焦例如为了触发 Tooltip与完全不可达两种行为的底层分界。测试用例 ToolbarRoot.test.tsx 验证了首个项被禁用且不可聚焦时初始 tab 停靠点会移到下一个可用项第 277-297 行验证了启用项即使设置了focusableWhenDisabled{false}仍可导航第 299-317 行验证了disabled focusableWhenDisabled{false}的 Input 会被直接跳过。Root 相关类型Root.StateToolbar.Root.Statetype ToolbarRootState { /** Whether the component is disabled. */ disabled: boolean; /** The component orientation. */ orientation: Toolbar.Root.Orientation; };Root.OrientationToolbar.Root.Orientationtype ToolbarRootOrientation horizontal | vertical;Root.ItemMetadataToolbar.Root.ItemMetadata对应源码中 ToolbarRoot.tsx 的接口type ToolbarRootItemMetadata { disabled: boolean; focusableWhenDisabled: boolean };Button可独立使用也可作为触发器Toolbar.Button是可以原样使用、也可以作为其他组件触发器的按钮渲染button元素。在 hero 示例 hero/tailwind/index.tsx/react/components/toolbar/demos/hero/tailwind/index.tsx#L45-L47) 中它通过render{Select.Trigger /}直接变成Select的下拉触发器同时在ToggleGroup中通过render{Toggle /}变成切换按钮——这是无样式组合能力的典型体现。Button PropsPropTypeDefaultDescriptionfocusableWhenDisabledbooleantrue为true时项在禁用状态下仍保持可聚焦。nativeButtonbooleantrue通过renderprop 替换元素时是否渲染原生button元素。如果被替换的元素不是按钮例如div请设为false。disabledbooleanfalse为true时禁用该项。classNamestring \| ((state: Toolbar.Button.State) string \| undefined)-CSS 类或其状态函数形式。styleReact.CSSProperties \| ((state: Toolbar.Button.State) React.CSSProperties \| undefined)-样式或其状态函数形式。renderReactElement \| ((props: HTMLProps, state: Toolbar.Button.State) ReactElement)-替换或组合底层元素。Button Data AttributesAttributeTypeDescriptiondata-orientationhorizontal \| vertical指示工具栏的方向。data-disabled-当按钮被禁用时出现。data-focusable-当按钮在禁用状态下仍可聚焦时出现。Button 的禁用级联与渲染细节在 ToolbarButton.tsx 中可以看到禁用状态的三级级联const disabled toolbarDisabled || (groupContext?.disabled ?? false) || disabledProp;即Root 禁用 → Group 禁用 → 自身disabledprop任一为真即视为禁用。实现上它借助内部useButton钩子见 packages/react/src/internals/use-button与CompositeItem完成disabled/aria-disabled/tabindex的管理。值得注意的一个细节ToolbarButton.tsx当提供了renderprop通常是另一个 Base UI 组件如Menu.Trigger时会向被渲染组件转发disabled使其能推导自己的禁用状态而默认按钮形态下不转发React 的disabled属性——这是为了保持可聚焦的禁用按钮仍可悬停例如用来触发 Tooltip。Button 相关类型type ToolbarButtonState { /** Whether the component is disabled. */ disabled: boolean; /** Whether the component remains focusable when disabled. */ focusable: boolean; /** The component orientation. */ orientation: Toolbar.Root.Orientation; };Input原生输入框接入工具栏导航Toolbar.Input是与 Toolbar 键盘导航集成的原生 input 元素渲染input。它让输入框也能参与 roving tabindex——方向键会把焦点在输入框与其他工具栏项之间移动而不是被输入框吞掉。Input PropsPropTypeDefaultDescriptiondefaultValuestring \| number \| string[]-输入框的默认值非受控。focusableWhenDisabledbooleantrue为true时项在禁用状态下仍保持可聚焦。disabledbooleanfalse为true时禁用该项。classNamestring \| ((state: Toolbar.Input.State) string \| undefined)-CSS 类或其状态函数形式。styleReact.CSSProperties \| ((state: Toolbar.Input.State) React.CSSProperties \| undefined)-样式或其状态函数形式。renderReactElement \| ((props: HTMLProps, state: Toolbar.Input.State) ReactElement)-替换或组合底层元素。Input Data AttributesAttributeTypeDescriptiondata-orientationhorizontal \| vertical指示工具栏的方向。data-disabled-当输入框被禁用时出现。data-focusable-当输入框在禁用状态下仍可聚焦时出现。Input 的禁用交互与源码实现ToolbarInput.tsx 与 Button 一样消费 Root / Group 上下文计算最终disabled并使用内部useFocusableWhenDisabled({ composite: true, ... })钩子见 packages/react/src/utils/useFocusableWhenDisabled生成禁用相关属性。此外禁用状态下它还会通过preventWhenDisabled拦截onClick与onPointerDownToolbarInput.tsx避免禁用输入框仍响应鼠标交互。注意与 Button 的差异Button 使用useButton内部会处理原生button的语义而 Input 显式传入isNativeButton: false——因为input不是按钮不能套用按钮的禁用-聚焦语义这正是useFocusableWhenDisabled存在的原因。Input 相关类型type ToolbarInputState { /** Whether the component is disabled. */ disabled: boolean; /** Whether the component remains focusable when disabled. */ focusable: boolean; /** The component orientation. */ orientation: Toolbar.Root.Orientation; };Group整组禁用与分组语义Toolbar.Group对若干工具栏项或切换进行分组渲染div元素。当工具栏中包含多个逻辑分区例如对齐组、数值格式组时用 Group 包裹可以同时获得语义与行为上的分组能力。Group PropsPropTypeDefaultDescriptiondisabledbooleanfalse为true时组内所有工具栏项都被禁用。classNamestring \| ((state: Toolbar.Group.State) string \| undefined)-CSS 类或其状态函数形式。styleReact.CSSProperties \| ((state: Toolbar.Group.State) React.CSSProperties \| undefined)-样式或其状态函数形式。renderReactElement \| ((props: HTMLProps, state: Toolbar.Group.State) ReactElement)-替换或组合底层元素。Group Data AttributesAttributeTypeDescriptiondata-orientationhorizontal \| vertical指示工具栏的方向。data-disabled-当组被禁用时出现。Group 的实现Context 级联与 rolegroupToolbarGroup.tsx 的计算逻辑为disabled toolbarDisabled || disabledProp并通过ToolbarGroupContext下发——这正是上一节 Button/Input 读取的groupContext?.disabled来源。DOM 上它会自动添加rolegroup配合aria-label即可为屏幕阅读器提供清晰的分组语义。测试 ToolbarRoot.test.tsx 也验证了Root 禁用时 Group 会带上data-disabled。type ToolbarGroupState { /** Whether the component is disabled. */ disabled: boolean; /** The component orientation. */ orientation: Toolbar.Root.Orientation; };Separator自动取反方向的分隔线Toolbar.Separator是对屏幕阅读器可访问的分隔元素渲染div。它默认方向与工具栏方向相反——水平工具栏渲染垂直分隔线垂直工具栏渲染水平分隔线——因此多数场景下你无需手动传orientation。Separator PropsPropTypeDefaultDescriptionorientationOrientation-分隔线的方向。默认取工具栏方向的相反值因此水平工具栏渲染垂直分隔线。classNamestring \| ((state: Toolbar.Separator.State) string \| undefined)-CSS 类或其状态函数形式。styleReact.CSSProperties \| ((state: Toolbar.Separator.State) React.CSSProperties \| undefined)-样式或其状态函数形式。renderReactElement \| ((props: HTMLProps, state: Toolbar.Separator.State) ReactElement)-替换或组合底层元素。Separator Data AttributesAttributeTypeDescriptiondata-orientationhorizontal \| vertical指示分隔线的方向与工具栏方向垂直。Separator 的实现ToolbarSeparator.tsx 直接读取 Root context 的orientation并取反后透传给通用的Separator组件const orientation context.orientation vertical ? horizontal : vertical; return Separator orientation{orientation} {...props} ref{forwardedRef} /;因此该部件实质上是base-ui/react/separator的薄封装其State也直接继承自通用 Separator 的SeparatorState。type ToolbarSeparatorState { /** The orientation of the separator. */ orientation: Orientation; };Link始终可聚焦的链接项Toolbar.Link是链接组件渲染a元素。在 hero 示例中它被用作工具栏右侧的Edited 51m ago超链接hero/tailwind/index.tsx/react/components/toolbar/demos/hero/tailwind/index.tsx#L80-L85)。Link PropsPropTypeDefaultDescriptionclassNamestring \| ((state: Toolbar.Link.State) string \| undefined)-CSS 类或其状态函数形式。styleReact.CSSProperties \| ((state: Toolbar.Link.State) React.CSSProperties \| undefined)-样式或其状态函数形式。renderReactElement \| ((props: React.DetailedHTMLPropsReact.AnchorHTMLAttributesHTMLAnchorElement, HTMLAnchorElement, state: Toolbar.Link.State) ReactElement)-替换或组合底层元素其 props 类型为a锚点元素的详细 HTML 属性。Link Data AttributesAttributeTypeDescriptiondata-orientationhorizontal \| vertical指示工具栏的方向。Link 的实现不能禁用的复合项ToolbarLink.tsx 使用固定元数据注册为复合项const TOOLBAR_LINK_METADATA { // Links cannot be disabled, but they still occupy a focusable composite item slot. disabled: false, focusableWhenDisabled: true, };即链接不能被禁用但它仍占据一个可聚焦的复合项槽位从而保持方向键导航的连续性。测试 ToolbarRoot.test.tsx 验证了Root 禁用时除链接外的所有项都会被禁用——Link 不会获得data-disabled或aria-disabled。type ToolbarLinkState { /** The component orientation. */ orientation: Toolbar.Root.Orientation; };键盘导航原理roving focus 与方向键Toolbar 的核心价值在于可访问的键盘导航。从测试矩阵ToolbarRoot.test.tsx可以完整还原其行为方向direction工具栏方向下一个键上一个键ltrhorizontalArrowRightArrowLeftltrverticalArrowDownArrowUprtlhorizontalArrowLeftArrowRightrtlverticalArrowDownArrowUp几点关键行为单 tab 停靠点整个工具栏只有一个tabindex0的项Tab 进入后其余项均为-1方向键在项之间移动焦点roving tabindex。跨层导航Group 内部的项与 Root 直接子项在同一导航序列中平铺——测试中顺序为 button1 → link → groupButton1 → groupButton2 → input。环绕wraploopFocus默认为true在末尾按下一个键会回到开头在开头按上一个键会跳到末尾loopFocus{false}时到达边界则停留原地ToolbarRoot.test.tsx。RTL 镜像水平工具栏在 RTL 环境下方向键自动镜像ArrowLeft 前进、ArrowRight 后退这由 Root 的dir与内部方向上下文共同决定。组合示例一个完整的富文本风格工具栏仓库提供的 hero 示例 hero/tailwind/index.tsx/react/components/toolbar/demos/hero/tailwind/index.tsx) 展示了 Toolbar 与ToggleGroup、Toggle、Select的组合用法其结构为Toolbar.Root ToggleGroup aria-labelAlignment Toolbar.Button render{Toggle /} valuealign-left aria-labelAlign left Align Left /Toolbar.Button Toolbar.Button render{Toggle /} valuealign-right aria-labelAlign right Align Right /Toolbar.Button /ToggleGroup Toolbar.Separator / Toolbar.Group aria-labelNumerical format Toolbar.Button aria-labelFormat as currency$/Toolbar.Button Toolbar.Button aria-labelFormat as percent%/Toolbar.Button /Toolbar.Group Toolbar.Separator / Select.Root defaultValueHelvetica Toolbar.Button render{Select.Trigger /}.../Toolbar.Button /Select.Root Toolbar.Separator / Toolbar.Link href#Edited 51m ago/Toolbar.Link /Toolbar.Root该示例同时印证了文中所有关键点Toggle/Select.Trigger通过render组合进工具栏、Separator自动取反方向、Group提供aria-label分组语义、Link作为始终可聚焦的复合项存在。该目录下还有对应的 CSS Modules 版本 hero/css-modules/index.tsx/react/components/toolbar/demos/hero/css-modules/index.tsx) 可供参考。导出组与 Canonical Types文档末尾还给出了类型系统的两种视角对应 index.ts 与 index.parts.ts 的导出结构Export Groups命名空间导出Toolbar.SeparatorToolbar.Separator、Toolbar.Separator.State、Toolbar.Separator.PropsToolbar.RootToolbar.Root、Toolbar.Root.ItemMetadata、Toolbar.Root.Orientation、Toolbar.Root.State、Toolbar.Root.PropsToolbar.GroupToolbar.Group、Toolbar.Group.State、Toolbar.Group.PropsToolbar.ButtonToolbar.Button、Toolbar.Button.State、Toolbar.Button.PropsToolbar.LinkToolbar.Link、Toolbar.Link.State、Toolbar.Link.PropsToolbar.InputToolbar.Input、Toolbar.Input.State、Toolbar.Input.PropsDefaultToolbar.Orientation、Orientation、ToolbarRootItemMetadata、ToolbarRootOrientation、ToolbarRootState、ToolbarRootProps、ToolbarGroupState、ToolbarGroupProps、ToolbarButtonState、ToolbarButtonProps、ToolbarLinkState、ToolbarLinkProps、ToolbarInputState、ToolbarInputProps、ToolbarSeparatorState、ToolbarSeparatorPropsCanonical Types别名映射当命名空间已导入时用 Canonical否则用 AliasCanonicalAliasToolbar.Separator.StateToolbarSeparatorStateToolbar.Separator.PropsToolbarSeparatorPropsToolbar.Root.ItemMetadataToolbarRootItemMetadataToolbar.Root.OrientationToolbarRootOrientationToolbar.Root.StateToolbarRootStateToolbar.Root.PropsToolbarRootPropsToolbar.Group.StateToolbarGroupStateToolbar.Group.PropsToolbarGroupPropsToolbar.Button.StateToolbarButtonStateToolbar.Button.PropsToolbarButtonPropsToolbar.Link.StateToolbarLinkStateToolbar.Link.PropsToolbarLinkPropsToolbar.Input.StateToolbarInputStateToolbar.Input.PropsToolbarInputProps此外还有一个通用类型Toolbar.Orientationtype ToolbarOrientation horizontal | vertical;它等价于Toolbar.Root.Orientation可用作Separator等部件的 orientation 参数。小结与最佳实践方向与环绕默认水平 loopFocus{true}是标准工具栏形态垂直工具栏记得设置orientationvertical分隔线方向会自动取反。禁用策略工具栏有三个层级的禁用——Root整栏、Group整组、单项配合默认focusableWhenDisabled{true}禁用项仍可聚焦以承载 Tooltip 等悬停交互需要完全不可达时对该项单独设置focusableWhenDisabled{false}。组合渲染renderprop 是 Base UI 组件组合的核心Toolbar.Button可无缝变成Toggle、Select.Trigger等用非按钮元素替换时记得nativeButton{false}。无障碍默认值Root 自动输出roletoolbar与aria-orientationGroup 输出rolegroupSeparator 对屏幕阅读器可访问建议为它们补充aria-label以提供完整语义。如需深入调试可直接阅读 ToolbarRoot.test.tsx 中的键盘导航与禁用态用例共 319 行它们相当于一份行为规格说明书文档本身则由pnpm docs:validate (docs)/react/components/toolbar从源码类型自动生成因此类型信息永远与 packages/react/src/toolbar 保持同步。【免费下载链接】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),仅供参考