Interface Lab
返回知识库
Frontend Implementation

ARIA 组件契约

自定义交互组件在写样式前要先定义 native-first 决策、name/role/value、ARIA/DOM 状态、键盘矩阵、焦点路径、视觉状态和测试断言。

设计原则

ARIA 不是给 div 补语义的魔法层;组件只有在原生语义、键盘行为、可访问名称、状态属性和视觉状态互相一致时,才是可实现的。

大理论

自定义组件的常见失败,是视觉已经像 button、tab、combobox 或 menu,但 DOM、键盘、焦点和读屏语义还停留在普通 div。aria-component-contract 的作用是在实现前冻结行为契约:先判断原生 HTML 能否完成任务,再选择成熟 primitive 或 APG 模式,最后才添加 ARIA、data-state 和样式。这样 agent 不会把 ARIA 当作最后补丁,而是把它作为组件状态机的一部分。

小知识点

  1. Native-first 是第一步:能用 buttonainputselecttextareadetails/summarydialog 时优先原生;原生不可表达复杂组合行为时才进入 ARIA/APG/primitive。
  2. 组件契约至少包含 role、accessible name、description、value、owned/controlled elements、keyboard keys、focus entry/exit、state attributes 和 failure states。
  3. Name/Role/Value 必须可测试:图标按钮要有名称,tab 要暴露 selected,switch/button pressed/checked 状态要和视觉一致,input invalid 要连接错误说明。
  4. ARIA 属性不能脱离行为:aria-expanded 必须对应真实展开内容,aria-selected 不等于当前页面链接,aria-disabled 不会自动阻止 click,aria-busy 需要可见状态和更新完成路径。
  5. 键盘矩阵要按组件类型写清:Tab 进入/退出,Enter/Space 激活,Arrow/Home/End 在复合组件内移动,Escape 关闭浮层或取消,typeahead 或 aria-activedescendant 只在模式匹配时使用。
  6. 焦点模型二选一并说明:DOM focus 在各项之间移动,或 focus 留在容器/input 上用 aria-activedescendant 暴露活动项;不要两个模型混用。
  7. 视觉状态要覆盖 hover、focus-visible、active、disabled、loading、invalid/error、selected/open/pressed、forced-colors 和 reduced-motion;关键状态不能只靠颜色或动效。
  8. Radix/shadcn/React Aria 等 primitive 的 label、description、portal、focus 和 data attributes 是行为边界;为了样式或动画改掉时必须有替代测试。
  9. 测试优先 getByRole/accessible name、键盘路径、state attribute、focus destination 和 live text;截图只能证明视觉,不能证明组件语义。

设计判断

如果一个组件契约能回答“为什么不用原生控件、用户按每个键发生什么、读屏听到的 name/role/value 是什么、视觉状态和 ARIA 状态如何同步、失败时如何恢复、测试如何断言”,它才适合进入前端实现。答不出来时,优先回退到原生控件、Radix/React Aria/Ariakit 或 APG 模式,而不是继续堆 role 和 CSS。

实现建议

输出 AriaComponentContract:componentType、userTask、nativeAlternative、chosenPattern、primitiveSource、role、accessibleNameSource、descriptionSource、valueModel、ownedElements、controlledElements、stateMap、keyboardMatrix、focusModel、portalContext、formIntegration、loadingPolicy、invalidPolicy、disabledPolicy、forcedColorsPolicy、reducedMotionPolicy、dataStateSelectors、testAssertions。React 实现中把 data-state 用于样式,把 ARIA 用于语义;不要让 CSS class 成为唯一状态来源。

反例/风险

div role=button 但没有 Space/Enter;图标按钮只有 SVG 没有名称;selected 视觉和 aria-selected 不一致;custom select 不能键盘选择;aria-disabled=true 但 click handler 仍执行;loading spinner 没有文本状态;错误只给红边框;Radix Dialog 动画破坏 focus return;虚拟列表中 aria-activedescendant 指向不存在节点。

案例分析

W3C Using ARIA 的第一原则是可用原生 HTML 时优先使用原生元素和属性;ARIA in HTML 进一步说明哪些角色/属性能和具体 HTML 元素组合。WCAG Name, Role, Value 要求用户界面组件的名称、角色、状态、属性和值可以由程序确定。Testing Library 的 ByRole 查询把 role 和 accessible name 作为测试入口,帮助把契约变成自动化断言。Radix Primitives 和 React Aria 的价值不是默认视觉,而是把复杂组件的 focus、keyboard、state 和 labelling 行为先稳定下来。可学习点是:可访问组件不是“加几个 aria 属性”,而是从 DOM 语义到测试查询都一致。

来源链接

W3C Using ARIA: https://www.w3.org/TR/using-aria/ W3C ARIA in HTML: https://www.w3.org/TR/html-aria/ WAI-ARIA APG Patterns: https://www.w3.org/WAI/ARIA/apg/patterns/ WCAG Name, Role, Value: https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html MDN ARIA guides: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Guides Testing Library ByRole: https://testing-library.com/docs/queries/byrole/ Radix Primitives Accessibility: https://www.radix-ui.com/primitives/docs/overview/accessibility Carbon Accessibility Testing: https://carbondesignsystem.com/guidelines/accessibility/testing/

Agent 指令

写自定义交互组件前必须先输出 native-first 决策、name/role/value、状态属性表、键盘矩阵、焦点模型、视觉状态和测试断言;禁止用 ARIA 修补没有键盘行为的 div 组件。