返回首页

可访问的 DatePicker React WCAG 2.2

这篇文章描述了在 React 上创建 WCAG 2.2 AA DatePicker 的过程,并使用 AI 生成修复。涵盖 APG 偏差、focus management、aria-live assertive。适用于高级开发者。

React 上的 WCAG DatePicker:从 AI 到理想
Advertisement 728x90

构建符合 WCAG 2.2 AA 标准的无障碍 React DatePicker

我们的开发团队从 Claude 生成的 DatePicker 代码入手,该代码基于 WAI-ARIA APG 的“日期选择对话框”模式。初始提示指定了 WCAG 2.1/2.2 AA 级要求:带有 aria-describedby 的输入框、触发按钮、role="dialog" 的模态对话框,以及基于网格的日历表格。然而,原始版本在焦点、导航和无障碍性方面暴露了严重问题。

AI 实现了基本的键盘导航(方向键、Home/End、PageUp/Down、Enter/Space、Escape)、网格单元上的 roving tabindex,以及月份变化时的 aria-live="polite"。但使用 VoiceOver 和 NVDA 测试时发现问题:打开时焦点未落在选中日期上,aria-live 公告排队堆积,对话框在重复点击时闪烁。

针对实际无障碍性的 APG 模式调整

APG 规范要求使用 <td role="gridcell"> 而非嵌套按钮,依赖手动 onClick/onKeyDown 和 tabindex。实际测试显示,在 <td> 内使用 <button> 有明显优势:

Google AdInline article slot
  • 原生焦点处理和 Enter/Space 支持,无需额外代码。
  • 屏幕阅读器自动语义识别。
  • 内置禁用状态,无需 aria-disabled 或 preventDefault。
  • 鼠标或键盘点击事件更可靠。

这是有意选择:APG 是指导,但设备测试优先。我们确保对比度 ≥4.5:1,使用 CSS Modules 和原生 Date/Intl API 进行格式化——除了 React/TypeScript 外无外部依赖。

修复功能和用户体验问题

原始代码无法编译:缺少 index.html 和 TypeScript 错误。修复后,又出现更多问题:

功能 Bug:

Google AdInline article slot
  • 超出 minDate/maxDate 的日期会跳转到其他月份,而不是显示禁用单元格。
  • 无法清空输入框。

视觉问题:

  • 单元格缺少焦点样式。
  • td 尺寸不均。

无障碍问题:

  • 打开时无焦点落在选中日期——屏幕阅读器保持沉默。
  • 月份头部 aria-live="polite" 在快速导航时导致公告排队。
  • 触发器失焦 + 点击导致对话框闪烁。

修复措施:

Google AdInline article slot
  • 打开时焦点落在值或今天;屏幕阅读器宣布上下文(星期、日期、月份、年份)。
  • 在独立隐藏区域使用 aria-live="assertive" 实现即时月份公告。
  • 选择后焦点返回触发器,并更新 aria-label。
  • 即使有禁用日期,也显示值的月份。
  • 移除 OK/取消:Enter 确认,Esc 关闭。

组件结构与键盘导航

Props: value (Date | null), onChange, minDate?, maxDate?, disabledDates?, locale?。

关键 ARIA:

  • 输入框:aria-describedby 说明格式,readonly。
  • 触发器:动态 aria-label(“已选:MM/DD/YYYY” 或 “选择日期”)。
  • 对话框:role="dialog",aria-modal="true",焦点陷阱。
  • 网格:table role="grid",td 内 button role="gridcell",值上 aria-selected,不可选日期上 aria-disabled。
  • 月份头部:在 polite 区域使用 aria-live="assertive"。

导航:

  • 方向键:相邻日期。
  • Home/End:月份首/末日。
  • PageUp/Down:±1 个月。
  • Shift+PageUp/Down:±1 年。
  • Enter/Space:选择。
  • Escape:关闭。

关键经验

  • AI 能快速搭建骨架(ARIA、导航),但屏幕阅读器测试必不可少。
  • <td> 内 <button> 在真实浏览器 + 屏幕阅读器中比纯 gridcell 更可靠。
  • aria-live="assertive" 避免月份导航时的公告排队。
  • 焦点管理:打开时定位值/今天,返回触发器。
  • 最小栈:React + TS,无库依赖。

该组件作为单日期选择器已生产就绪,开源代码便于扩展(年份、范围)。

— Editorial Team

Advertisement 728x90

继续阅读