Radix Vue 的 ToggleGroupItem 组件深度解析Props、Slots 与源码实现【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vueToggleGroupItem 是 radix-vue即 Reka UI中 ToggleGroup切换组的核心组成单元——一个可被按下/弹起的双态two-state按钮。本文以docs/content/meta/ToggleGroupItem.md的 API 文档为主体结合 ToggleGroupItem.vue 与 ToggleGroupRoot.vue 的源码实现完整讲解该组件的 Props、Slots、数据属性、键盘交互与底层工作原理帮助你掌握在单选框式或复选框式的切换组中正确使用、定制和扩展 Item 的完整方案。ToggleGroupItem 在 ToggleGroup 中的定位ToggleGroup 被官方文档描述为A set of two-state buttons that can be toggled on or off一组可以开启或关闭的双态按钮适用于文本对齐、粗体/斜体、视图切换等工具条场景。它由两部分组成ToggleGroupRoot包含整个切换组的所有部分负责维护选中值、方向、焦点管理与表单控制ToggleGroupItem组内的单个可切换项也是本文的主角。在 toggle-group.md 中官方列出的核心特性包括完整键盘导航、支持水平/垂直方向、支持单选与多选、可受控也可不受控。而 ToggleGroupItem 正是承载单个按钮可切换这一语义的组件其完整的组件脉络可参考 toggle-group.md 与 ToggleGroupRoot.md。根据当前仓库文档安装后通过如下方式导入并使用script setup import { ToggleGroupItem, ToggleGroupRoot } from reka-ui /script template ToggleGroupRoot ToggleGroupItem / /ToggleGroupRoot /templateProps 详解ToggleGroupItem 共暴露 4 个 Props其中value为必填项其余均可选。完整表格如下源自 ToggleGroupItem.mdNameDescriptionTypeRequiredDefaultasThe element or component this component should render as. Can be overwritten by asChild.AsTag \| ComponentNobuttonasChildChange the default rendered element for the one passed as a child, merging their props and behavior.booleanNo-disabledWhen true, prevents the user from interacting with the toggle.booleanNo-valueA string value for the toggle group item. All items within a toggle group should use a unique value.AcceptableValueYes-value必填唯一的项标识value: AcceptableValue是唯一必填的 Props用于标识当前 Item 在组内的身份。官方明确要求同一个 ToggleGroup 内所有 Item 的 value 必须唯一。在单选typesingle模式下当前被激活项的 value 就是 Root 的modelValue在多选typemultiple模式下Root 的modelValue是激活项的 value 数组。值得注意的一个细节是虽然文档描述其为 A string value但源码中其类型为AcceptableValue见 ToggleGroupItem.vue因此实际支持字符串之外更宽泛的可接受值类型你可以依据使用场景传入合适的值。as / asChild控制最终渲染元素as: AsTag | Component决定组件渲染为何种元素或组件默认值为button。从源码看Item 的默认渲染正是依赖withDefaults(definePropsToggleGroupItemProps(), { as: button })实现的asChild: boolean允许将渲染元素替换为传入的子元素并把 Item 的 props 与行为合并到该子元素上。这一机制在源码中体现为外层Primitive的as-child转发常用于将原生按钮、图标按钮或自定义组件作为切换项的可点击外壳。需要特别说明asChild可以覆盖as指定的元素二者配合即可在不牺牲可访问性与行为的前提下完全掌控 DOM 输出结构。disabled禁用单个 Itemdisabled: boolean置为true时用户无法与该切换项交互。它的语义是单 Item 级的禁用与 Root 上的全局disabled禁用整个组是两层独立控制。源码中的处理逻辑如下ToggleGroupItem.vueconst disabled computed(() rootContext.disabled?.value || props.disabled)即 Item 自身的disabled与 Root 注入的disabled取或关系任一为真该 Item 即处于禁用态。此外禁用态还会同步透传到焦点管理层面——当rovingFocus开启时focusable: !disabled使禁用项从键盘 Tab 序列中被排除见下文源码解析。Slots 详解ToggleGroupItem 的默认插槽会向调用方暴露 4 个作用域插槽属性用于渲染图标、文本或按状态切换样式源自 ToggleGroupItem.mdNameDescriptionTypemodelValueCurrent valuebooleanstateCurrent stateon \| offpressedCurrent pressed statebooleandisabledCurrent disabled stateboolean典型用法是通过解构插槽属性来按状态渲染内容或应用类名ToggleGroupItem v-foritem in items :keyitem.value :valueitem.value template #default{ pressed, state, disabled } Icon :classpressed ? text-primary : / span{{ state on ? 已激活 : 未激活 }}/span /template /ToggleGroupItem这些插槽属性并非 Item 自行发明而是直接透传自底层 Toggle.vue 的默认插槽定义modelValue当前布尔值、stateon | off字符串态、pressed按压态布尔值、disabled禁用态。Item 在此之上通过v-slotslotProps原样转发ToggleGroupItem.vue因此你看到的插槽属性类型与 Toggle 完全一致。源码实现Item 如何与 Root 协作阅读 ToggleGroupItem.vue 的模板部分可以看到 Item 是一个典型的高阶组合组件其内部结构可以拆解为三层RovingFocusItem 或 Primitive焦点管理/基础渲染外壳 └── Toggle底层双态按钮aria-pressed、data-state、click 切换 └── slot透传插槽属性1. 焦点外壳RovingFocusItem 与 Primitive 的动态切换component :isrootContext.rovingFocus.value ? RovingFocusItem : Primitive as-child v-bindrootContext.rovingFocus.value ? { focusable: !disabled, active: pressed } : {} 当 Root 的rovingFocus为true默认值时Item 外层渲染为RovingFocusItem从而加入 Roving Tabindex 焦点管理组当rovingFocus为false时则退化为普通Primitive。同时focusable: !disabled保证禁用项不可聚焦active: pressed将当前已激活状态同步给焦点管理使 Tab 键能优先聚焦到已激活项。2. 状态判定isValueEqualOrExistItem 是否处于按下状态由当前组值是否包含该 Item 的 value 决定const pressed computed(() isValueEqualOrExist(rootContext.modelValue.value, props.value))isValueEqualOrExist定义于 isValueEqualOrExist.ts会先判空再按基础值是数组则检查数组内是否存在当前值、否则做全等比较的规则返回布尔结果。因此在单选模式下pressed (modelValue value)在多选模式下pressed modelValue.includes(value)一个工具函数同时覆盖两种模式。3. 点击回调changeModelValueItem 的切换行为统一收敛到 Root 提供的changeModelValue(value)方法ToggleGroupRoot.vue 经由provideToggleGroupRootContext注入。该方法定义在 useSingleOrMultipleValue.ts 中单选模式若新值与当前值相等则置为undefined取消选择否则直接替换为新值多选模式拷贝当前数组若已包含该值则删除去选否则追加选中。这套单选 toggle 可取消、多选 toggle 可增删的语义与双态按钮的预期行为完全一致。4. 禁用态与样式作用域禁用态disabled rootContext.disabled || props.disabledRoot 级与 Item 级取或样式作用域由于 Item 包装了多根节点的Toggle父组件的 scoped 样式 id 不会自动透传源码通过useForwardScopeId()手动传递ToggleGroupItem.vue保证消费者写style scoped时依然能命中内部元素。Root 的 Props 如何影响 ItemItem 的行为高度依赖 Root 的配置完整 Root 配置见 ToggleGroupRoot.md。其中与 Item 直接相关的关键项包括Root Props默认值对 Item 的影响typesingle或由modelValue/defaultValue推断决定 Item 是单选排他还是多选叠加见useSingleOrMultipleValue的类型推断逻辑modelValue/defaultValue-受控/不受控模式的初始值直接决定哪些 Item 的pressed为真disabledfalse一键禁用组内全部 ItemrovingFocustrue控制 Item 是否纳入 Roving Tabindex 焦点管理looptrue与rovingFocus配合键盘导航是否首尾循环orientation-horizontal时左右方向键切换焦点vertical时上下方向键切换dir继承ConfigProvider默认ltr影响方向键的焦点移动方向值得强调的类型推断机制useSingleOrMultipleValue会先校验modelValue/defaultValue是否为数组是数组即推断为multiple否则为single只有两者都未提供时才回落到显式的typeprop最终兜底single。所以单选还是多选本质上由 Root 的值形态决定而 Item 无需关心。实战示例示例一受控单选确保始终有一个值被选中官方文档给出的Ensuring there is always a value示例toggle-group.md通过控制 Root 的值保证用户永远无法取消全部选中项。script setup import { ToggleGroupItem, ToggleGroupRoot } from reka-ui import { ref } from vue const value ref(left) /script template ToggleGroupRoot :model-valuevalue update:model-value(val) { if (val) value val } ToggleGroupItem valueleft TextAlignLeftIcon / /ToggleGroupItem ToggleGroupItem valuecenter TextAlignCenterIcon / /ToggleGroupItem ToggleGroupItem valueright TextAlignRightIcon / /ToggleGroupItem /ToggleGroupRoot /template这里update:model-value中if (val) value val的含义是当用户点击当前已激活项Root 会尝试将其置为undefined时拒绝接受空值从而维持组内始终有且仅有一个激活项。示例二多选模式多选只需让 Root 的值形态为数组例如typemultipleItem 的 value 会被增删到数组中template ToggleGroupRoot typemultiple v-modelfilters ToggleGroupItem valuebold加粗/ToggleGroupItem ToggleGroupItem valueitalic斜体/ToggleGroupItem ToggleGroupItem valueunderline下划线/ToggleGroupItem /ToggleGroupRoot /template示例三组合 asChild 与插槽自定义将 Item 渲染为自定义子元素并按state应用样式ToggleGroupRoot typesingle v-modeltheme ToggleGroupItem as-child valuelight button classpx-4 py-2 :data-stateundefined浅色/button /ToggleGroupItem ToggleGroupItem as-child valuedark button classpx-4 py-2深色/button /ToggleGroupItem /ToggleGroupRoot使用asChild时Item 会把aria-pressed、data-state与点击行为合并到子元素上因此建议对子元素保留语义化标签如原生button。数据属性Data AttributesItem 渲染后会在元素上暴露以下数据属性便于通过 CSS 选择器按状态定制样式源自 toggle-group.mdAttributeValues[data-state]on/off[data-disabled]Present when disabled存在即表示禁用[data-orientation]vertical/horizontal其中[data-state]与[data-disabled]来源于底层 Toggle.vue 的绑定data-state由modelValue计算得出真值为on假值为offdata-disabled仅在禁用时存在[data-orientation]则由 Root 统一注入。键盘交互与可访问性ToggleGroup 使用 WAI-ARIA 的 roving tabindex 模式管理焦点见 toggle-group.md 的 Accessibility 章节Item 是键盘导航的直接参与单元KeysDescriptionTabMoves focus to either the pressed item or the first item in the group.SpaceActivates/deactivates the item.EnterActivates/deactivates the item.ArrowDownMoves focus to the next item in the group.ArrowRightMoves focus to the next item in the group.ArrowUpMoves focus to the previous item in the group.ArrowLeftMoves focus to the previous item in the group.HomeMoves focus to the first item.EndMoves focus to the last item.在无障碍语义上Item 复用 Toggle 的aria-pressed属性Toggle.vue使屏幕阅读器能够准确播报按下/未按下状态同时根元素默认携带rolegroupToggleGroupRoot.vue组合成符合 ARIA button pattern 的可访问组件。方向键的具体移动方向会受orientation、dirLTR/RTL与loop影响。小结ToggleGroupItem 虽然 API 表面只有 4 个 Props 和 4 个插槽属性但它的实现融合了 Primitive 组合、Roving Tabindex 焦点管理、单/多选值模型与表单语义等多层能力value是唯一必填项组内必须唯一它是 Item 与 Root 值模型之间的坐标as/asChild提供渲染自由度disabled提供单 Item 级禁用插槽暴露的modelValue/state/pressed/disabled全部来自底层 Toggle可直接驱动条件渲染键盘导航、data-state等行为由 Root 的rovingFocus、orientation、loop、dir等配置统一调度。若需进一步深入可继续阅读 toggle-group.md组件总览与完整键盘表、ToggleGroupRoot.mdRoot 全部 Props以及 Toggle.vueItem 底层实现它们共同构成了对 ToggleGroup 体系的完整认识。【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考