资讯动态

Reka UI(radix-vue)DrawerTitle 详解:抽屉组件的可访问性标题与 ARIA 标签机制

发布时间:2026/9/17 14:09:12 来源:尧图企业网站定制
Reka UIradix-vueDrawerTitle 详解抽屉组件的可访问性标题与 ARIA 标签机制【免费下载链接】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本篇聚焦 radix-vuereka-ui中 Drawer 组件的DrawerTitle部件它是抽屉打开时供屏幕阅读器朗读的可访问性标题默认渲染为h2并通过自动生成的id与DrawerContent的aria-labelledby属性建立 ARIA 关联。读完本篇你将理解DrawerTitle的完整 API、它在 Dialog WAI-ARIA 模式中的职责、其id是如何生成并注入的以及如何在视觉上隐藏标题但不丢失无障碍信息。一、DrawerTitle 在 Drawer 中的定位Drawer 是一个从屏幕边缘滑入的面板遵循 WAI-ARIA 的 Dialog 模式参见 drawer.md 中aria: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/的声明。该模式要求弹窗类内容必须有一个可被辅助技术识别的标题——这正是DrawerTitle的职责打开时朗读文档明确描述它是 An accessible title to be announced when the drawer is opened打开时会被朗读的可访问性标题。不是视觉标题的替代品它就是一个普通的标题元素你通常同时用它承担视觉上的抽屉标题例如 Edit profile。与DrawerDescription配套DrawerDescription提供可选的描述文案DrawerTitle则是必需的——缺少它时开发环境会给出警告见下文源码分析。在 drawer.md 的 Anatomy 小节中DrawerTitle是标准装配结构的一部分script setup import { DrawerClose, DrawerContent, DrawerDescription, DrawerHandle, DrawerOverlay, DrawerPortal, DrawerRoot, DrawerTitle, DrawerTrigger, } from reka-ui /script template DrawerRoot DrawerTrigger / DrawerPortal DrawerOverlay / DrawerContent DrawerHandle / DrawerTitle / DrawerDescription / DrawerClose / /DrawerContent /DrawerPortal /DrawerRoot /template二、Props API根据 DrawerTitle 元文档DrawerTitle只有两个属性NameDescriptionTypeRequiredDefaultasThe element or component this component should render as. Can be overwritten byasChild.AsTag \| ComponentNoh2asChildChange the default rendered element for the one passed as a child, merging their props and behavior. 详见官方的 Composition 指南booleanNo-两点使用提示默认渲染为h2as的默认值是h2即组件默认输出一个h2元素这与 Dialog 模式中标题应使用标题语义元素的建议一致。若你的页面标题层级需要调整例如抽屉出现在h3层级下可通过as属性覆盖或配合as-child将属性合并到你的自定义元素上。asChild合并行为开启asChild后组件不渲染默认标签而是把id、as等属性与子元素的 props 合并这是 reka-ui 的组合/Composition 机制。典型场景就是下文要讲的VisuallyHidden asChild包裹。三、源码级实现一个极简的ID 绑定器阅读 DrawerTitle.vue 的完整实现仅 20 余行script setup langts import { Primitive } from /Primitive import { useForwardExpose } from /shared import { injectDrawerRootContext } from ./DrawerRoot.vue const props withDefaults(definePropsDrawerTitleProps(), { as: h2 }) const rootContext injectDrawerRootContext() useForwardExpose() /script template Primitive v-bindprops :idrootContext.titleId slot / /Primitive /template从源码结构看DrawerTitle本身几乎没有业务逻辑它做且只做三件事通过Primitive渲染底层是 reka-ui 的 Primitive它支持as/asChild多态渲染——这就是 API 表中两个属性生效的位置。withDefaults中{ as: h2 }与文档声明的默认值h2完全对应。从 Root 上下文取titleId并绑定为id:idrootContext.titleId是整条 ARIA 关联链的关键一环。id不由用户手动指定而是由DrawerRoot统一生成见下节保证同一棵组件树内标题、描述、内容三者引用关系稳定。useForwardExpose()通过 shared 中的工具把组件内部元素 ref 暴露给父级便于外部拿到底层 DOM。它不注入role、不添加aria-*属性——因为h2元素本身就携带标题语义多余的声明反而破坏语义。四、titleId 的生成机制useId三级回退titleId来自 DrawerRoot.vue 中对共享工具 useId 的调用// packages/core/src/Drawer/DrawerRoot.vue const contentId useId(undefined, reka-drawer-content) const titleId useId(undefined, reka-drawer-title) const descriptionId useId(undefined, reka-drawer-description)useId的生成策略在 useId.ts 中是一个三级回退链若显式传入deterministicId则直接返回否则优先使用ConfigProvider注入的useId源——注释说明这是为了让 Nuxt 等框架在预渲染prerender与客户端水合时使用一致的 ID 前缀避免水合不匹配再否则使用 Vue 3.5 原生的vue.useId()最终兜底是自增计数器let count 0id ${count}。返回值为${prefix}-${id}形式因此实际 DOM 中的标题 id 形如reka-drawer-title-xxxx。这一设计意味着你无需也不应手动给DrawerTitle写id手动指定反而可能破坏aria-labelledby指向。五、ARIA 关联链roledialog如何指向标题在 DrawerContentImpl.vue 的模板中可以看到这条关联链的终点DismissableLayer :idrootContext.contentId roledialog :aria-describedbyrootContext.descriptionId :aria-labelledbyrootContext.titleId !-- 对应 :aria-labelledbyrootContext.titleId -- ... 完整链路为DrawerRoot 生成 titleIduseId │ 通过 provide/inject 上下文下发 ▼ DrawerTitle 渲染 h2 :idtitleId ▲ │ id 字符串相同 DrawerContent 输出 div roledialog aria-labelledbytitleId屏幕阅读器在聚焦roledialog元素时会解析aria-labelledby找到该h2并将其文本朗读出来——这就是 announced when the drawer is opened 的底层机制。aria-describedby同理指向DrawerDescription。缺少 DrawerTitle 时的开发期警告DrawerContentImpl内置了一个针对本部件的防御性检查DrawerContentImpl.vue#L347-L356// Dev warning for missing DrawerTitle if (process.env.NODE_ENV ! production) { onMounted(() { if (!document.getElementById(rootContext.titleId)) { console.warn( Warning: \DrawerContent\ requires a \DrawerTitle\ for accessibility., ) } }) }非生产环境下若DrawerContent挂载后 DOM 中找不到对应的titleId元素控制台会输出Warning: DrawerContent requires a DrawerTitle for accessibility.。测试文件 Drawer.test.ts 中专门定义了NoTitleDrawerTest刻意不含DrawerTitle的抽屉用于覆盖这条路径。这与drawer.md的特性列表相呼应Manages screen reader announcements withTitleandDescriptioncomponents。实践结论DrawerTitle应始终存在不需要可见标题时也应保留用视觉隐藏方式见下节。六、隐藏标题但不丢失无障碍信息drawer.md 在 Title 小节给出的官方做法是If you want to hide the title, wrap it inside our Visually Hidden utility like thisVisuallyHidden asChild。即用库自带的 VisuallyHidden 包裹DrawerContent VisuallyHidden as-child DrawerTitleEdit profile/DrawerTitle /VisuallyHidden !-- 视觉上由自定义元素承担标题展示 但该元素仍通过 asChild 机制挂上自动生成的 id 吗 ——注意id 挂在 DrawerTitle 上因此被隐藏的 h2 才是被朗读的节点 -- /DrawerContentVisuallyHidden的默认feature为focusable它通过position: absolute、width/height: 1px、clip: rect(0, 0, 0, 0)、clipPath: inset(50%)等样式把元素移出视觉范围但不设置display: none或visibility: hidden因此元素仍在无障碍树中aria-labelledby指向的文本依然可被朗读。若传featurefully-hidden则同时加aria-hiddentrue与tabindex-1那样标题就会被辅助技术忽略——在需要被朗读的场景下不要使用该模式。七、实战用法完整示例文档站自带的 CSS 版示例 docs/components/demo/Drawer/css/index.vue 展示了标准写法script setup import { DrawerClose, DrawerContent, DrawerDescription, DrawerHandle, DrawerOverlay, DrawerPortal, DrawerRoot, DrawerTitle, DrawerTrigger, } from reka-ui import ./styles.css /script template DrawerRoot DrawerTrigger classButton grass Open Drawer /DrawerTrigger DrawerPortal DrawerOverlay classDrawerOverlay / DrawerContent classDrawerContent DrawerHandle classDrawerHandle / div classDrawerBody !-- DrawerTitle 默认渲染为 h2 idreka-drawer-title-xxx -- DrawerTitle classDrawerTitle Edit profile /DrawerTitle DrawerDescription classDrawerDescription Make changes to your profile here. Swipe down or click close when youre done. /DrawerDescription !-- ... 表单内容 ... -- div classDrawerFooter DrawerClose as-child button classButton greenSave changes/button /DrawerClose /div /div /DrawerContent /DrawerPortal /DrawerRoot /template注意示例中DrawerTitle同时承担了视觉标题带DrawerTitleclass与无障碍标题双重角色这是最常见的用法。由于 Drawer 是 unstyled 原语DrawerTitle的字号、间距完全由你的 CSS 控制Tailwind 版示例见 docs/components/demo/Drawer/tailwind/index.vue。若想在视觉层级上不用h2可用as覆盖或as-child合并!-- 方式一改标签 -- DrawerTitle ash3Edit profile/DrawerTitle !-- 方式二属性合并进自定义元素 -- DrawerTitle as-child h3 classmy-titleEdit profile/h3 /DrawerTitle八、小结DrawerTitle的 API 极简as/asChild默认h2核心职责是可访问性它是DrawerContentroledialog的aria-labelledby目标。id由DrawerRoot通过useId自动生成前缀reka-drawer-title支持ConfigProvider自定义 ID 源以保证 SSR/水合一致不要手动指定。DrawerContent缺少DrawerTitle时会在开发环境打印无障碍警告且有对应测试用例覆盖生产环境建议始终提供标题。需要隐藏标题时用VisuallyHidden as-child包裹保持其在无障碍树中可被朗读VisuallyHidden的featurefully-hidden模式则会彻底隐藏勿混淆。【免费下载链接】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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价