Back

模态框常见的无障碍问题(及其解决方案)

模态框常见的无障碍问题(及其解决方案)

模态框在现代 Web 应用程序中随处可见,但它们也是无障碍功能失效最常见的原因之一。模态框是一个出现在主内容之上的对话框,需要用户交互才能继续操作。如果实现不当,模态框可能会困住键盘用户、混淆屏幕阅读器,并为任何依赖辅助技术的人创造令人沮丧的体验。让我们来探讨模态框最常见的无障碍问题及其实用解决方案。

关键要点

  • 焦点管理至关重要:打开时将焦点移至模态框,关闭时返回到触发元素
  • 使用正确的 ARIA 属性,包括 role=“dialog”、aria-modal=“true” 和无障碍标签
  • 实现完整的键盘导航,支持 Tab 键循环和 Escape 键
  • 在模态框打开时让背景内容完全不可交互
  • 使用真实的辅助技术进行测试,而不仅仅是自动化工具

焦点管理的关键作用

模态框最严重的无障碍问题是焦点管理失效。当模态框打开时,焦点应该立即移动到模态框本身——通常是第一个可交互元素或模态框容器。当它关闭时,焦点必须返回到触发它的元素。

常见错误: 焦点仍留在背景内容上,允许用户通过 Tab 键浏览模态框后面的元素。

解决方案: 实现正确的焦点管理:

// 打开模态框时
const triggerElement = document.activeElement;
modal.showModal(); // 或者对于自定义实现使用 modal.focus()

// 关闭模态框时
modal.close();
triggerElement.focus();

对于使用 focus-trap-react 的 React 应用程序:

import FocusTrap from 'focus-trap-react';

function Modal({ isOpen, onClose, children }) {
  return (
    <FocusTrap active={isOpen}>
      <div role="dialog" aria-modal="true">
        {children}
      </div>
    </FocusTrap>
  );
}

缺失或错误的 ARIA 属性

屏幕阅读器需要关于模态框对话框的明确信息才能正确宣读它们。缺失或误用的 ARIA 属性会让用户对模态框的用途和状态感到困惑。

常见错误:

  • 没有 role="dialog"role="alertdialog"
  • 缺少 aria-modal="true"
  • 没有通过 aria-labelaria-labelledby 提供无障碍名称

解决方案: 使用适当的 ARIA 属性:

<!-- 使用原生 dialog 元素(推荐) -->
<dialog aria-labelledby="modal-title" aria-describedby="modal-desc">
  <h2 id="modal-title">删除确认</h2>
  <p id="modal-desc">此操作无法撤销。</p>
</dialog>

<!-- 使用带有 ARIA 的 div -->
<div role="dialog" 
     aria-modal="true" 
     aria-labelledby="modal-title"
     aria-describedby="modal-desc">
  <!-- 模态框内容 -->
</div>

对于需要立即用户响应的关键警告,使用 role="alertdialog",因为它会触发更强制性的屏幕阅读器宣读。

键盘导航失效

每个模态框都必须完全支持键盘无障碍。用户应该能够使用 Tab/Shift+Tab 在可交互元素之间导航,并使用 Escape 键关闭模态框。

常见错误:

  • 不支持 Escape 键
  • 焦点可能逃离模态框(没有焦点陷阱)
  • Tab 顺序与视觉布局不匹配

解决方案: 实现完整的焦点陷阱:

function trapFocus(modalElement) {
  const focusableElements = modalElement.querySelectorAll(
    'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
  );
  
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];
  
  modalElement.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
      closeModal();
      return;
    }
    
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === firstElement) {
        e.preventDefault();
        lastElement.focus();
      } else if (!e.shiftKey && document.activeElement === lastElement) {
        e.preventDefault();
        firstElement.focus();
      }
    }
  });
  
  firstElement.focus();
}

背景内容仍可交互

当模态框打开时,背景内容应该完全不可交互。用户不应该能够与模态框后面的任何内容进行交互。

常见错误: 背景仍可滚动或通过键盘导航进行交互。

解决方案: 让背景内容不可交互:

// 打开模态框时
document.body.style.overflow = 'hidden';
document.querySelector('main').setAttribute('aria-hidden', 'true');
document.querySelector('main').setAttribute('inert', ''); // 现代方法

// 关闭模态框时
document.body.style.overflow = '';
document.querySelector('main').removeAttribute('aria-hidden');
document.querySelector('main').removeAttribute('inert');

测试模态框无障碍性

自动化工具可以捕获一些问题,但手动测试仍然至关重要:

  1. 键盘测试:

    • 打开模态框并验证焦点移动到它
    • 通过 Tab 键浏览所有可交互元素
    • 确保 Tab 键在模态框内循环
    • 按 Escape 键关闭
    • 验证焦点返回到触发元素
  2. 屏幕阅读器测试:

    • 使用 NVDA(Windows)或 VoiceOver(macOS)进行测试
    • 验证模态框角色被宣读
    • 检查标题和描述是否被读取
    • 确保背景内容不可访问
  3. 视觉测试:

    • 验证焦点指示器可见
    • 检查颜色对比度(WCAG AA 要求普通文本为 4.5:1,大文本和 UI 组件为 3:1)
    • 确保关闭按钮有清晰的标签

实现最佳实践

尽可能使用原生的 <dialog> 元素。 它提供内置的焦点管理和 ARIA 语义:

const dialog = document.querySelector('dialog');
dialog.showModal(); // 以适当的焦点陷阱打开
dialog.close();     // 关闭并返回焦点

对于自定义实现,请遵循此检查清单:

  • 设置 role="dialog"aria-modal="true"
  • 通过 aria-labelledbyaria-label 提供无障碍名称
  • 实现完整的键盘支持(Tab、Shift+Tab、Escape)
  • 创建强大的焦点陷阱
  • 禁用背景滚动和交互
  • 关闭时将焦点返回到触发元素
  • 包含可见的焦点指示器
  • 使用实际的辅助技术进行测试

结论

无障碍模态框不仅仅是为了合规——它们为所有用户创造更好的体验。通过解决这些常见问题,您确保您的模态框对键盘用户、屏幕阅读器用户以及其他所有人都能无缝工作。从语义化 HTML 开始,添加适当的 ARIA 属性,实现全面的焦点管理,并始终使用真实的辅助技术进行测试。

记住:如果您的模态框在没有鼠标的情况下无法工作,那么它就是无效的。修复这些问题,您的模态框将真正对每个人都无障碍。

常见问题

对于包含表单或信息的标准模态框使用 role='dialog'。对于需要立即响应的关键警告使用 role='alertdialog',因为它会让屏幕阅读器更强制性地宣读内容并中断用户当前的任务。

CSS 可以通过覆盖层在视觉上遮蔽内容并阻止指针事件,但它无法阻止键盘导航。您需要 JavaScript 来添加 aria-hidden 或 inert 属性,以真正让背景内容对所有用户都不可交互。

最佳实践是将焦点移到第一个可交互元素,如果关闭按钮在顶部,通常是关闭按钮。但是,对于包含关键信息的模态框,首先聚焦标题可确保屏幕阅读器用户在执行任何操作之前听到标题。

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay