本章概述

可访问性(Accessibility,简称 a11y)是现代 Web 开发中不可忽视的重要方面。本章将深入探讨如何使用 Bootstrap 创建无障碍的 Web 应用,确保所有用户,包括残障人士,都能平等地访问和使用我们的网站。

学习目标

  • 理解 Web 可访问性的重要性和基本原则
  • 掌握 Bootstrap 中的可访问性特性
  • 学会使用 ARIA 标签和属性
  • 实现键盘导航和屏幕阅读器支持
  • 掌握颜色对比度和视觉设计的可访问性
  • 学会测试和验证可访问性

Web 可访问性基础

1. 可访问性原则 (WCAG 2.1)

Web 内容可访问性指南(WCAG)定义了四个基本原则:

POUR 原则

  1. 可感知(Perceivable)

    • 信息和用户界面组件必须以用户能够感知的方式呈现
    • 提供替代文本、字幕、音频描述等
  2. 可操作(Operable)

    • 用户界面组件和导航必须是可操作的
    • 支持键盘导航、避免引起癫痫的内容
  3. 可理解(Understandable)

    • 信息和用户界面的操作必须是可理解的
    • 使用清晰的语言、提供帮助信息
  4. 健壮(Robust)

    • 内容必须足够健壮,能够被各种用户代理解释
    • 使用有效的 HTML、支持辅助技术

2. 常见的可访问性障碍

<!-- 可访问性问题示例 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>可访问性问题示例</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        /* 问题:颜色对比度不足 */
        .low-contrast {
            color: #999;
            background-color: #ccc;
        }
        
        /* 问题:仅依赖颜色传达信息 */
        .error-text {
            color: red;
        }
        
        /* 问题:字体太小 */
        .tiny-text {
            font-size: 10px;
        }
    </style>
</head>
<body>
    <div class="container mt-4">
        <!-- 问题:缺少语义化标签 -->
        <div class="header">
            <div class="title">网站标题</div>
            <div class="nav">
                <div class="nav-item">首页</div>
                <div class="nav-item">关于</div>
                <div class="nav-item">联系</div>
            </div>
        </div>
        
        <!-- 问题:缺少替代文本 -->
        <img src="important-chart.png" class="img-fluid">
        
        <!-- 问题:不可访问的表单 -->
        <form>
            <input type="text" placeholder="请输入姓名">
            <input type="email" placeholder="请输入邮箱">
            <button type="submit">提交</button>
        </form>
        
        <!-- 问题:不可访问的模态框 -->
        <button class="btn btn-primary" onclick="showModal()">打开模态框</button>
        <div id="myModal" style="display: none;">
            <div>模态框内容</div>
            <button onclick="hideModal()">关闭</button>
        </div>
        
        <!-- 问题:低对比度文本 -->
        <p class="low-contrast">这段文字的对比度不足,难以阅读。</p>
        
        <!-- 问题:仅用颜色表示错误 -->
        <p class="error-text">这是错误信息</p>
        
        <!-- 问题:字体过小 -->
        <p class="tiny-text">这段文字太小了</p>
    </div>
    
    <script>
        function showModal() {
            document.getElementById('myModal').style.display = 'block';
        }
        
        function hideModal() {
            document.getElementById('myModal').style.display = 'none';
        }
    </script>
</body>
</html>

Bootstrap 可访问性特性

1. 语义化 HTML 结构

Bootstrap 鼓励使用语义化的 HTML 元素:

<!-- 正确的语义化结构 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>可访问的网站结构</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <!-- 使用 header 元素 -->
    <header class="bg-primary text-white">
        <div class="container">
            <nav class="navbar navbar-expand-lg navbar-dark">
                <a class="navbar-brand" href="#">网站名称</a>
                <button class="navbar-toggler" type="button" 
                        data-bs-toggle="collapse" 
                        data-bs-target="#navbarNav"
                        aria-controls="navbarNav" 
                        aria-expanded="false" 
                        aria-label="切换导航">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarNav">
                    <ul class="navbar-nav ms-auto">
                        <li class="nav-item">
                            <a class="nav-link active" aria-current="page" href="#">首页</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">关于我们</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">服务</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">联系我们</a>
                        </li>
                    </ul>
                </div>
            </nav>
        </div>
    </header>
    
    <!-- 使用 main 元素 -->
    <main class="container my-5">
        <!-- 使用 section 和 article 元素 -->
        <section aria-labelledby="welcome-heading">
            <h1 id="welcome-heading">欢迎来到我们的网站</h1>
            <p class="lead">这是一个注重可访问性的网站示例。</p>
        </section>
        
        <section aria-labelledby="services-heading">
            <h2 id="services-heading">我们的服务</h2>
            <div class="row">
                <article class="col-md-4">
                    <div class="card h-100">
                        <img src="service1.jpg" class="card-img-top" alt="网站开发服务图片">
                        <div class="card-body">
                            <h3 class="card-title">网站开发</h3>
                            <p class="card-text">专业的网站开发服务,注重用户体验和可访问性。</p>
                        </div>
                    </div>
                </article>
                <article class="col-md-4">
                    <div class="card h-100">
                        <img src="service2.jpg" class="card-img-top" alt="移动应用开发服务图片">
                        <div class="card-body">
                            <h3 class="card-title">移动应用</h3>
                            <p class="card-text">跨平台移动应用开发,支持 iOS 和 Android。</p>
                        </div>
                    </div>
                </article>
                <article class="col-md-4">
                    <div class="card h-100">
                        <img src="service3.jpg" class="card-img-top" alt="UI/UX设计服务图片">
                        <div class="card-body">
                            <h3 class="card-title">UI/UX 设计</h3>
                            <p class="card-text">用户界面和用户体验设计,注重可用性和美观性。</p>
                        </div>
                    </div>
                </article>
            </div>
        </section>
    </main>
    
    <!-- 使用 footer 元素 -->
    <footer class="bg-dark text-white py-4">
        <div class="container">
            <div class="row">
                <div class="col-md-6">
                    <h4>联系信息</h4>
                    <address>
                        <p>地址:北京市朝阳区某某街道123号</p>
                        <p>电话:<a href="tel:+8610-12345678" class="text-white">010-12345678</a></p>
                        <p>邮箱:<a href="mailto:info@example.com" class="text-white">info@example.com</a></p>
                    </address>
                </div>
                <div class="col-md-6">
                    <h4>快速链接</h4>
                    <ul class="list-unstyled">
                        <li><a href="#" class="text-white">隐私政策</a></li>
                        <li><a href="#" class="text-white">服务条款</a></li>
                        <li><a href="#" class="text-white">网站地图</a></li>
                    </ul>
                </div>
            </div>
            <hr class="my-3">
            <div class="text-center">
                <p>&copy; 2024 公司名称. 保留所有权利。</p>
            </div>
        </div>
    </footer>
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

2. 内置的 ARIA 支持

Bootstrap 组件内置了许多 ARIA 属性:

<!-- Bootstrap 组件的 ARIA 支持 -->
<div class="container mt-4">
    <!-- 折叠面板 (Accordion) -->
    <div class="accordion" id="accessibleAccordion">
        <div class="accordion-item">
            <h2 class="accordion-header" id="headingOne">
                <button class="accordion-button" type="button" 
                        data-bs-toggle="collapse" 
                        data-bs-target="#collapseOne" 
                        aria-expanded="true" 
                        aria-controls="collapseOne">
                    第一个折叠项
                </button>
            </h2>
            <div id="collapseOne" 
                 class="accordion-collapse collapse show" 
                 aria-labelledby="headingOne" 
                 data-bs-parent="#accessibleAccordion">
                <div class="accordion-body">
                    这是第一个折叠项的内容。Bootstrap 自动添加了适当的 ARIA 属性。
                </div>
            </div>
        </div>
        
        <div class="accordion-item">
            <h2 class="accordion-header" id="headingTwo">
                <button class="accordion-button collapsed" type="button" 
                        data-bs-toggle="collapse" 
                        data-bs-target="#collapseTwo" 
                        aria-expanded="false" 
                        aria-controls="collapseTwo">
                    第二个折叠项
                </button>
            </h2>
            <div id="collapseTwo" 
                 class="accordion-collapse collapse" 
                 aria-labelledby="headingTwo" 
                 data-bs-parent="#accessibleAccordion">
                <div class="accordion-body">
                    这是第二个折叠项的内容。
                </div>
            </div>
        </div>
    </div>
    
    <!-- 标签页 (Tabs) -->
    <div class="mt-5">
        <ul class="nav nav-tabs" id="accessibleTabs" role="tablist">
            <li class="nav-item" role="presentation">
                <button class="nav-link active" 
                        id="home-tab" 
                        data-bs-toggle="tab" 
                        data-bs-target="#home" 
                        type="button" 
                        role="tab" 
                        aria-controls="home" 
                        aria-selected="true">
                    首页
                </button>
            </li>
            <li class="nav-item" role="presentation">
                <button class="nav-link" 
                        id="profile-tab" 
                        data-bs-toggle="tab" 
                        data-bs-target="#profile" 
                        type="button" 
                        role="tab" 
                        aria-controls="profile" 
                        aria-selected="false">
                    个人资料
                </button>
            </li>
            <li class="nav-item" role="presentation">
                <button class="nav-link" 
                        id="contact-tab" 
                        data-bs-toggle="tab" 
                        data-bs-target="#contact" 
                        type="button" 
                        role="tab" 
                        aria-controls="contact" 
                        aria-selected="false">
                    联系方式
                </button>
            </li>
        </ul>
        
        <div class="tab-content" id="accessibleTabsContent">
            <div class="tab-pane fade show active" 
                 id="home" 
                 role="tabpanel" 
                 aria-labelledby="home-tab">
                <div class="p-3">
                    <h3>首页内容</h3>
                    <p>这是首页标签的内容。</p>
                </div>
            </div>
            <div class="tab-pane fade" 
                 id="profile" 
                 role="tabpanel" 
                 aria-labelledby="profile-tab">
                <div class="p-3">
                    <h3>个人资料</h3>
                    <p>这是个人资料标签的内容。</p>
                </div>
            </div>
            <div class="tab-pane fade" 
                 id="contact" 
                 role="tabpanel" 
                 aria-labelledby="contact-tab">
                <div class="p-3">
                    <h3>联系方式</h3>
                    <p>这是联系方式标签的内容。</p>
                </div>
            </div>
        </div>
    </div>
    
    <!-- 模态框 (Modal) -->
    <div class="mt-5">
        <button type="button" 
                class="btn btn-primary" 
                data-bs-toggle="modal" 
                data-bs-target="#accessibleModal">
            打开可访问的模态框
        </button>
        
        <div class="modal fade" 
             id="accessibleModal" 
             tabindex="-1" 
             aria-labelledby="accessibleModalLabel" 
             aria-hidden="true">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title" id="accessibleModalLabel">可访问的模态框</h5>
                        <button type="button" 
                                class="btn-close" 
                                data-bs-dismiss="modal" 
                                aria-label="关闭">
                        </button>
                    </div>
                    <div class="modal-body">
                        <p>这是一个可访问的模态框示例。它包含了适当的 ARIA 属性和键盘导航支持。</p>
                        <form>
                            <div class="mb-3">
                                <label for="modalInput" class="form-label">输入内容</label>
                                <input type="text" class="form-control" id="modalInput">
                            </div>
                        </form>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
                        <button type="button" class="btn btn-primary">保存</button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

ARIA 标签和属性详解

1. 常用 ARIA 属性

<!-- ARIA 属性详解 -->
<div class="container mt-4">
    <!-- aria-label: 为元素提供可访问的名称 -->
    <button class="btn btn-primary" aria-label="关闭对话框">
        <i class="fas fa-times" aria-hidden="true"></i>
    </button>
    
    <!-- aria-labelledby: 引用其他元素作为标签 -->
    <div class="card">
        <div class="card-header">
            <h3 id="card-title">用户信息</h3>
        </div>
        <div class="card-body" aria-labelledby="card-title">
            <p>这里是用户的详细信息。</p>
        </div>
    </div>
    
    <!-- aria-describedby: 引用描述性文本 -->
    <div class="mb-3">
        <label for="password" class="form-label">密码</label>
        <input type="password" 
               class="form-control" 
               id="password" 
               aria-describedby="passwordHelp">
        <div id="passwordHelp" class="form-text">
            密码必须包含至少8个字符,包括大小写字母和数字。
        </div>
    </div>
    
    <!-- aria-expanded: 表示可折叠元素的状态 -->
    <div class="dropdown">
        <button class="btn btn-secondary dropdown-toggle" 
                type="button" 
                id="dropdownMenuButton" 
                data-bs-toggle="dropdown" 
                aria-expanded="false">
            下拉菜单
        </button>
        <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton">
            <li><a class="dropdown-item" href="#">操作1</a></li>
            <li><a class="dropdown-item" href="#">操作2</a></li>
            <li><a class="dropdown-item" href="#">操作3</a></li>
        </ul>
    </div>
    
    <!-- aria-hidden: 隐藏装饰性元素 -->
    <p>
        <i class="fas fa-star" aria-hidden="true"></i>
        重要信息
    </p>
    
    <!-- aria-live: 动态内容更新通知 -->
    <div class="alert alert-info" role="alert" aria-live="polite" id="statusMessage">
        状态信息将在这里显示
    </div>
    
    <!-- aria-invalid: 表示表单字段验证状态 -->
    <div class="mb-3">
        <label for="email" class="form-label">邮箱地址</label>
        <input type="email" 
               class="form-control is-invalid" 
               id="email" 
               aria-invalid="true" 
               aria-describedby="emailError">
        <div id="emailError" class="invalid-feedback">
            请输入有效的邮箱地址。
        </div>
    </div>
    
    <!-- role 属性: 定义元素的语义角色 -->
    <div role="alert" class="alert alert-danger">
        这是一个重要的错误消息。
    </div>
    
    <nav role="navigation" aria-label="面包屑导航">
        <ol class="breadcrumb">
            <li class="breadcrumb-item"><a href="#">首页</a></li>
            <li class="breadcrumb-item"><a href="#">产品</a></li>
            <li class="breadcrumb-item active" aria-current="page">详情</li>
        </ol>
    </nav>
</div>

<script>
// 动态更新 aria-live 区域
function updateStatus(message) {
    const statusElement = document.getElementById('statusMessage');
    statusElement.textContent = message;
}

// 示例:3秒后更新状态
setTimeout(() => {
    updateStatus('操作已成功完成!');
}, 3000);
</script>

2. 复杂组件的 ARIA 实现

<!-- 复杂组件的可访问性实现 -->
<div class="container mt-4">
    <!-- 可访问的数据表格 -->
    <div class="table-responsive">
        <table class="table table-striped" 
               role="table" 
               aria-label="用户数据表">
            <caption class="visually-hidden">
                用户数据表,包含姓名、邮箱、角色和操作列
            </caption>
            <thead>
                <tr role="row">
                    <th scope="col" role="columnheader">
                        <button class="btn btn-link p-0 text-start" 
                                aria-label="按姓名排序">
                            姓名
                            <i class="fas fa-sort" aria-hidden="true"></i>
                        </button>
                    </th>
                    <th scope="col" role="columnheader">
                        <button class="btn btn-link p-0 text-start" 
                                aria-label="按邮箱排序">
                            邮箱
                            <i class="fas fa-sort" aria-hidden="true"></i>
                        </button>
                    </th>
                    <th scope="col" role="columnheader">角色</th>
                    <th scope="col" role="columnheader">操作</th>
                </tr>
            </thead>
            <tbody>
                <tr role="row">
                    <td role="cell">张三</td>
                    <td role="cell">zhangsan@example.com</td>
                    <td role="cell">
                        <span class="badge bg-primary">管理员</span>
                    </td>
                    <td role="cell">
                        <div class="btn-group" role="group" aria-label="用户操作">
                            <button class="btn btn-sm btn-outline-primary" 
                                    aria-label="编辑张三的信息">
                                <i class="fas fa-edit" aria-hidden="true"></i>
                                编辑
                            </button>
                            <button class="btn btn-sm btn-outline-danger" 
                                    aria-label="删除张三">
                                <i class="fas fa-trash" aria-hidden="true"></i>
                                删除
                            </button>
                        </div>
                    </td>
                </tr>
                <tr role="row">
                    <td role="cell">李四</td>
                    <td role="cell">lisi@example.com</td>
                    <td role="cell">
                        <span class="badge bg-secondary">用户</span>
                    </td>
                    <td role="cell">
                        <div class="btn-group" role="group" aria-label="用户操作">
                            <button class="btn btn-sm btn-outline-primary" 
                                    aria-label="编辑李四的信息">
                                <i class="fas fa-edit" aria-hidden="true"></i>
                                编辑
                            </button>
                            <button class="btn btn-sm btn-outline-danger" 
                                    aria-label="删除李四">
                                <i class="fas fa-trash" aria-hidden="true"></i>
                                删除
                            </button>
                        </div>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    
    <!-- 可访问的分页组件 -->
    <nav aria-label="页面导航">
        <ul class="pagination justify-content-center">
            <li class="page-item disabled">
                <a class="page-link" href="#" tabindex="-1" aria-disabled="true">
                    <span aria-hidden="true">&laquo;</span>
                    <span class="visually-hidden">上一页</span>
                </a>
            </li>
            <li class="page-item active" aria-current="page">
                <a class="page-link" href="#">
                    1
                    <span class="visually-hidden">(当前页)</span>
                </a>
            </li>
            <li class="page-item">
                <a class="page-link" href="#" aria-label="转到第2页">2</a>
            </li>
            <li class="page-item">
                <a class="page-link" href="#" aria-label="转到第3页">3</a>
            </li>
            <li class="page-item">
                <a class="page-link" href="#" aria-label="下一页">
                    <span aria-hidden="true">&raquo;</span>
                    <span class="visually-hidden">下一页</span>
                </a>
            </li>
        </ul>
    </nav>
    
    <!-- 可访问的进度条 -->
    <div class="mb-3">
        <label for="file-upload-progress" class="form-label">文件上传进度</label>
        <div class="progress" role="progressbar" 
             aria-valuenow="75" 
             aria-valuemin="0" 
             aria-valuemax="100" 
             aria-labelledby="file-upload-progress">
            <div class="progress-bar" style="width: 75%">
                <span class="visually-hidden">75% 完成</span>
            </div>
        </div>
        <div class="form-text">已上传 75%</div>
    </div>
    
    <!-- 可访问的警告框 -->
    <div class="alert alert-warning alert-dismissible fade show" 
         role="alert" 
         aria-live="assertive">
        <strong>注意!</strong> 这是一个重要的警告信息。
        <button type="button" 
                class="btn-close" 
                data-bs-dismiss="alert" 
                aria-label="关闭警告">
        </button>
    </div>
</div>

键盘导航支持

1. 键盘导航基础

<!-- 键盘导航支持 -->
<div class="container mt-4">
    <!-- 跳转链接 -->
    <a href="#main-content" class="visually-hidden-focusable">跳转到主要内容</a>
    <a href="#navigation" class="visually-hidden-focusable">跳转到导航</a>
    
    <nav id="navigation" class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
            <a class="navbar-brand" href="#">网站名称</a>
            <div class="navbar-nav">
                <a class="nav-link" href="#" tabindex="0">首页</a>
                <a class="nav-link" href="#" tabindex="0">关于</a>
                <a class="nav-link" href="#" tabindex="0">服务</a>
                <a class="nav-link" href="#" tabindex="0">联系</a>
            </div>
        </div>
    </nav>
    
    <main id="main-content" class="mt-4">
        <!-- 可键盘操作的卡片组 -->
        <div class="row">
            <div class="col-md-4">
                <div class="card keyboard-card" 
                     tabindex="0" 
                     role="button" 
                     aria-label="产品A详情">
                    <img src="product-a.jpg" class="card-img-top" alt="产品A图片">
                    <div class="card-body">
                        <h5 class="card-title">产品A</h5>
                        <p class="card-text">这是产品A的描述信息。</p>
                    </div>
                </div>
            </div>
            <div class="col-md-4">
                <div class="card keyboard-card" 
                     tabindex="0" 
                     role="button" 
                     aria-label="产品B详情">
                    <img src="product-b.jpg" class="card-img-top" alt="产品B图片">
                    <div class="card-body">
                        <h5 class="card-title">产品B</h5>
                        <p class="card-text">这是产品B的描述信息。</p>
                    </div>
                </div>
            </div>
            <div class="col-md-4">
                <div class="card keyboard-card" 
                     tabindex="0" 
                     role="button" 
                     aria-label="产品C详情">
                    <img src="product-c.jpg" class="card-img-top" alt="产品C图片">
                    <div class="card-body">
                        <h5 class="card-title">产品C</h5>
                        <p class="card-text">这是产品C的描述信息。</p>
                    </div>
                </div>
            </div>
        </div>
        
        <!-- 可键盘操作的表单 -->
        <form class="mt-5" novalidate>
            <fieldset>
                <legend>联系表单</legend>
                
                <div class="row">
                    <div class="col-md-6">
                        <div class="mb-3">
                            <label for="firstName" class="form-label">姓名 *</label>
                            <input type="text" 
                                   class="form-control" 
                                   id="firstName" 
                                   required 
                                   aria-required="true">
                            <div class="invalid-feedback">
                                请输入您的姓名。
                            </div>
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="mb-3">
                            <label for="email" class="form-label">邮箱 *</label>
                            <input type="email" 
                                   class="form-control" 
                                   id="email" 
                                   required 
                                   aria-required="true">
                            <div class="invalid-feedback">
                                请输入有效的邮箱地址。
                            </div>
                        </div>
                    </div>
                </div>
                
                <div class="mb-3">
                    <label for="subject" class="form-label">主题</label>
                    <select class="form-select" id="subject">
                        <option value="">请选择主题</option>
                        <option value="general">一般咨询</option>
                        <option value="support">技术支持</option>
                        <option value="sales">销售咨询</option>
                    </select>
                </div>
                
                <div class="mb-3">
                    <label for="message" class="form-label">消息</label>
                    <textarea class="form-control" 
                              id="message" 
                              rows="4" 
                              aria-describedby="messageHelp"></textarea>
                    <div id="messageHelp" class="form-text">
                        请详细描述您的问题或需求。
                    </div>
                </div>
                
                <div class="mb-3">
                    <div class="form-check">
                        <input class="form-check-input" 
                               type="checkbox" 
                               id="newsletter" 
                               value="yes">
                        <label class="form-check-label" for="newsletter">
                            订阅我们的新闻通讯
                        </label>
                    </div>
                </div>
                
                <div class="mb-3">
                    <fieldset>
                        <legend class="form-label">联系方式偏好</legend>
                        <div class="form-check">
                            <input class="form-check-input" 
                                   type="radio" 
                                   name="contactPreference" 
                                   id="preferEmail" 
                                   value="email" 
                                   checked>
                            <label class="form-check-label" for="preferEmail">
                                邮箱
                            </label>
                        </div>
                        <div class="form-check">
                            <input class="form-check-input" 
                                   type="radio" 
                                   name="contactPreference" 
                                   id="preferPhone" 
                                   value="phone">
                            <label class="form-check-label" for="preferPhone">
                                电话
                            </label>
                        </div>
                    </fieldset>
                </div>
                
                <button type="submit" class="btn btn-primary">提交</button>
                <button type="reset" class="btn btn-secondary ms-2">重置</button>
            </fieldset>
        </form>
    </main>
</div>

<style>
/* 键盘焦点样式 */
.keyboard-card:focus {
    outline: 2px solid #0d6efd;
    outline-offset: 2px;
    box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}

/* 跳转链接样式 */
.visually-hidden-focusable:not(:focus):not(:focus-within) {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

.visually-hidden-focusable:focus {
    position: static !important;
    width: auto !important;
    height: auto !important;
    padding: 0.25rem 0.5rem !important;
    margin: 0 !important;
    overflow: visible !important;
    clip: auto !important;
    white-space: normal !important;
    background-color: #0d6efd;
    color: white;
    text-decoration: none;
    border-radius: 0.25rem;
}
</style>

<script>
// 键盘导航支持
document.addEventListener('DOMContentLoaded', function() {
    // 为卡片添加键盘事件
    const cards = document.querySelectorAll('.keyboard-card');
    
    cards.forEach(card => {
        card.addEventListener('keydown', function(e) {
            if (e.key === 'Enter' || e.key === ' ') {
                e.preventDefault();
                // 模拟点击事件
                console.log('卡片被激活:', this.getAttribute('aria-label'));
                // 这里可以添加实际的点击处理逻辑
            }
        });
    });
    
    // 表单验证
    const form = document.querySelector('form');
    
    form.addEventListener('submit', function(e) {
        e.preventDefault();
        
        const firstName = document.getElementById('firstName');
        const email = document.getElementById('email');
        
        let isValid = true;
        
        // 验证姓名
        if (!firstName.value.trim()) {
            firstName.classList.add('is-invalid');
            firstName.setAttribute('aria-invalid', 'true');
            isValid = false;
        } else {
            firstName.classList.remove('is-invalid');
            firstName.setAttribute('aria-invalid', 'false');
        }
        
        // 验证邮箱
        const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!email.value.trim() || !emailPattern.test(email.value)) {
            email.classList.add('is-invalid');
            email.setAttribute('aria-invalid', 'true');
            isValid = false;
        } else {
            email.classList.remove('is-invalid');
            email.setAttribute('aria-invalid', 'false');
        }
        
        if (isValid) {
            alert('表单提交成功!');
        } else {
            // 将焦点移到第一个错误字段
            const firstError = form.querySelector('.is-invalid');
            if (firstError) {
                firstError.focus();
            }
        }
    });
});
</script>

2. 高级键盘导航模式

// advanced-keyboard-navigation.js
// 高级键盘导航管理器

class AdvancedKeyboardNavigation {
  constructor() {
    this.focusableElements = [
      'a[href]',
      'button:not([disabled])',
      'input:not([disabled])',
      'select:not([disabled])',
      'textarea:not([disabled])',
      '[tabindex]:not([tabindex="-1"])',
      '[contenteditable="true"]'
    ].join(', ');
    
    this.init();
  }
  
  // 初始化键盘导航
  init() {
    this.setupFocusManagement();
    this.setupArrowKeyNavigation();
    this.setupEscapeKeyHandling();
    this.setupFocusTrapping();
  }
  
  // 设置焦点管理
  setupFocusManagement() {
    // 为所有可聚焦元素添加焦点指示器
    document.addEventListener('focusin', (e) => {
      this.showFocusIndicator(e.target);
    });
    
    document.addEventListener('focusout', (e) => {
      this.hideFocusIndicator(e.target);
    });
    
    // 检测键盘导航
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Tab') {
        document.body.classList.add('keyboard-navigation');
      }
    });
    
    document.addEventListener('mousedown', () => {
      document.body.classList.remove('keyboard-navigation');
    });
  }
  
  // 设置箭头键导航
  setupArrowKeyNavigation() {
    // 为网格布局添加箭头键导航
    document.addEventListener('keydown', (e) => {
      const target = e.target;
      
      if (target.hasAttribute('data-grid-navigation')) {
        this.handleGridNavigation(e, target);
      }
      
      if (target.hasAttribute('data-list-navigation')) {
        this.handleListNavigation(e, target);
      }
    });
  }
  
  // 处理网格导航
  handleGridNavigation(e, target) {
    const container = target.closest('[data-grid-container]');
    if (!container) return;
    
    const items = Array.from(container.querySelectorAll('[data-grid-navigation]'));
    const currentIndex = items.indexOf(target);
    const columns = parseInt(container.getAttribute('data-grid-columns')) || 3;
    
    let newIndex = currentIndex;
    
    switch (e.key) {
      case 'ArrowRight':
        newIndex = Math.min(currentIndex + 1, items.length - 1);
        break;
      case 'ArrowLeft':
        newIndex = Math.max(currentIndex - 1, 0);
        break;
      case 'ArrowDown':
        newIndex = Math.min(currentIndex + columns, items.length - 1);
        break;
      case 'ArrowUp':
        newIndex = Math.max(currentIndex - columns, 0);
        break;
      case 'Home':
        newIndex = 0;
        break;
      case 'End':
        newIndex = items.length - 1;
        break;
      default:
        return;
    }
    
    if (newIndex !== currentIndex) {
      e.preventDefault();
      items[newIndex].focus();
    }
  }
  
  // 处理列表导航
  handleListNavigation(e, target) {
    const container = target.closest('[data-list-container]');
    if (!container) return;
    
    const items = Array.from(container.querySelectorAll('[data-list-navigation]'));
    const currentIndex = items.indexOf(target);
    
    let newIndex = currentIndex;
    
    switch (e.key) {
      case 'ArrowDown':
        newIndex = (currentIndex + 1) % items.length;
        break;
      case 'ArrowUp':
        newIndex = (currentIndex - 1 + items.length) % items.length;
        break;
      case 'Home':
        newIndex = 0;
        break;
      case 'End':
        newIndex = items.length - 1;
        break;
      default:
        return;
    }
    
    if (newIndex !== currentIndex) {
      e.preventDefault();
      items[newIndex].focus();
    }
  }
  
  // 设置 Escape 键处理
  setupEscapeKeyHandling() {
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') {
        this.handleEscapeKey(e);
      }
    });
  }
  
  // 处理 Escape 键
  handleEscapeKey(e) {
    // 关闭模态框
    const openModal = document.querySelector('.modal.show');
    if (openModal) {
      const closeButton = openModal.querySelector('[data-bs-dismiss="modal"]');
      if (closeButton) {
        closeButton.click();
      }
      return;
    }
    
    // 关闭下拉菜单
    const openDropdown = document.querySelector('.dropdown-menu.show');
    if (openDropdown) {
      const toggle = document.querySelector('[data-bs-toggle="dropdown"][aria-expanded="true"]');
      if (toggle) {
        toggle.click();
      }
      return;
    }
    
    // 退出搜索模式
    const searchInput = document.querySelector('input[type="search"]:focus');
    if (searchInput && searchInput.value) {
      searchInput.value = '';
      searchInput.dispatchEvent(new Event('input'));
      return;
    }
  }
  
  // 设置焦点陷阱
  setupFocusTrapping() {
    // 为模态框设置焦点陷阱
    document.addEventListener('shown.bs.modal', (e) => {
      this.trapFocus(e.target);
    });
    
    document.addEventListener('hidden.bs.modal', (e) => {
      this.releaseFocus(e.target);
    });
  }
  
  // 陷阱焦点
  trapFocus(container) {
    const focusableElements = container.querySelectorAll(this.focusableElements);
    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];
    
    // 设置初始焦点
    if (firstElement) {
      firstElement.focus();
    }
    
    // 添加键盘事件监听器
    const trapHandler = (e) => {
      if (e.key === 'Tab') {
        if (e.shiftKey) {
          // Shift + Tab
          if (document.activeElement === firstElement) {
            e.preventDefault();
            lastElement.focus();
          }
        } else {
          // Tab
          if (document.activeElement === lastElement) {
            e.preventDefault();
            firstElement.focus();
          }
        }
      }
    };
    
    container.addEventListener('keydown', trapHandler);
    container._focusTrapHandler = trapHandler;
  }
  
  // 释放焦点
  releaseFocus(container) {
    if (container._focusTrapHandler) {
      container.removeEventListener('keydown', container._focusTrapHandler);
      delete container._focusTrapHandler;
    }
    
    // 恢复之前的焦点
    const trigger = document.querySelector('[data-bs-target="#' + container.id + '"]');
    if (trigger) {
      trigger.focus();
    }
  }
  
  // 显示焦点指示器
  showFocusIndicator(element) {
    if (document.body.classList.contains('keyboard-navigation')) {
      element.classList.add('keyboard-focus');
    }
  }
  
  // 隐藏焦点指示器
  hideFocusIndicator(element) {
    element.classList.remove('keyboard-focus');
  }
  
  // 获取所有可聚焦元素
  getFocusableElements(container = document) {
    return Array.from(container.querySelectorAll(this.focusableElements))
      .filter(element => {
        return element.offsetWidth > 0 && 
               element.offsetHeight > 0 && 
               !element.disabled &&
               element.tabIndex !== -1;
      });
  }
  
  // 移动焦点到下一个元素
  focusNext(currentElement) {
    const focusableElements = this.getFocusableElements();
    const currentIndex = focusableElements.indexOf(currentElement);
    const nextIndex = (currentIndex + 1) % focusableElements.length;
    focusableElements[nextIndex].focus();
  }
  
  // 移动焦点到上一个元素
  focusPrevious(currentElement) {
    const focusableElements = this.getFocusableElements();
    const currentIndex = focusableElements.indexOf(currentElement);
    const previousIndex = (currentIndex - 1 + focusableElements.length) % focusableElements.length;
    focusableElements[previousIndex].focus();
  }
  
  // 移动焦点到第一个元素
  focusFirst(container = document) {
    const focusableElements = this.getFocusableElements(container);
    if (focusableElements.length > 0) {
      focusableElements[0].focus();
    }
  }
  
  // 移动焦点到最后一个元素
  focusLast(container = document) {
    const focusableElements = this.getFocusableElements(container);
    if (focusableElements.length > 0) {
      focusableElements[focusableElements.length - 1].focus();
    }
  }
}

// 创建全局实例
const keyboardNavigation = new AdvancedKeyboardNavigation();

// 导出
export default keyboardNavigation;

颜色对比度与视觉设计

1. 颜色对比度标准

<!-- 颜色对比度示例 -->
<div class="container mt-4">
    <h2>颜色对比度示例</h2>
    
    <!-- 良好的对比度 -->
    <div class="row mb-4">
        <div class="col-md-6">
            <h3>良好的对比度 (WCAG AA 级别)</h3>
            
            <div class="p-3 mb-3" style="background-color: #ffffff; color: #212529;">
                <strong>对比度比例: 16.75:1</strong><br>
                这是黑色文字在白色背景上的示例,具有极佳的对比度。
            </div>
            
            <div class="p-3 mb-3" style="background-color: #0d6efd; color: #ffffff;">
                <strong>对比度比例: 5.9:1</strong><br>
                这是白色文字在 Bootstrap 主色调背景上的示例。
            </div>
            
            <div class="p-3 mb-3" style="background-color: #198754; color: #ffffff;">
                <strong>对比度比例: 4.7:1</strong><br>
                这是白色文字在绿色背景上的示例。
            </div>
        </div>
        
        <div class="col-md-6">
            <h3>对比度不足的示例 (避免使用)</h3>
            
            <div class="p-3 mb-3" style="background-color: #f8f9fa; color: #adb5bd;">
                <strong>对比度比例: 2.3:1 ❌</strong><br>
                这个对比度太低,难以阅读。
            </div>
            
            <div class="p-3 mb-3" style="background-color: #ffc107; color: #ffffff;">
                <strong>对比度比例: 1.8:1 ❌</strong><br>
                黄色背景上的白色文字对比度不足。
            </div>
            
            <div class="p-3 mb-3" style="background-color: #20c997; color: #28a745;">
                <strong>对比度比例: 1.2:1 ❌</strong><br>
                相似颜色之间的对比度极低。
            </div>
        </div>
    </div>
    
    <!-- 改进的颜色方案 -->
    <div class="row">
        <div class="col-12">
            <h3>改进的颜色方案</h3>
            
            <div class="alert alert-success" role="alert">
                <strong>成功!</strong> 操作已成功完成。
                <button type="button" class="btn btn-outline-success btn-sm ms-2">查看详情</button>
            </div>
            
            <div class="alert alert-warning" role="alert">
                <strong>警告!</strong> 请注意以下事项。
                <button type="button" class="btn btn-outline-dark btn-sm ms-2">了解更多</button>
            </div>
            
            <div class="alert alert-danger" role="alert">
                <strong>错误!</strong> 操作失败,请重试。
                <button type="button" class="btn btn-outline-danger btn-sm ms-2">重试</button>
            </div>
        </div>
    </div>
</div>

2. 可访问的颜色系统

// accessible-colors.scss
// 可访问的颜色系统

// 基础颜色定义
$white: #ffffff;
$black: #000000;
$gray-100: #f8f9fa;
$gray-200: #e9ecef;
$gray-300: #dee2e6;
$gray-400: #ced4da;
$gray-500: #adb5bd;
$gray-600: #6c757d;
$gray-700: #495057;
$gray-800: #343a40;
$gray-900: #212529;

// 主题颜色 (符合 WCAG AA 标准)
$primary: #0d6efd;
$primary-dark: #0a58ca;  // 用于白色文字
$secondary: #6c757d;
$success: #198754;
$info: #0dcaf0;
$info-dark: #087990;     // 用于白色文字
$warning: #fd7e14;       // 改进的警告色
$warning-dark: #e55100;  // 用于白色文字
$danger: #dc3545;
$light: #f8f9fa;
$dark: #212529;

// 对比度检查函数
@function contrast-ratio($color1, $color2) {
  $l1: luminance($color1);
  $l2: luminance($color2);
  
  @if $l1 > $l2 {
    @return ($l1 + 0.05) / ($l2 + 0.05);
  } @else {
    @return ($l2 + 0.05) / ($l1 + 0.05);
  }
}

@function luminance($color) {
  $red: red($color) / 255;
  $green: green($color) / 255;
  $blue: blue($color) / 255;
  
  $red: if($red <= 0.03928, $red / 12.92, pow(($red + 0.055) / 1.055, 2.4));
  $green: if($green <= 0.03928, $green / 12.92, pow(($green + 0.055) / 1.055, 2.4));
  $blue: if($blue <= 0.03928, $blue / 12.92, pow(($blue + 0.055) / 1.055, 2.4));
  
  @return 0.2126 * $red + 0.7152 * $green + 0.0722 * $blue;
}

// 自动选择文字颜色
@function auto-text-color($bg-color, $light-color: $white, $dark-color: $black) {
  $light-contrast: contrast-ratio($bg-color, $light-color);
  $dark-contrast: contrast-ratio($bg-color, $dark-color);
  
  @if $light-contrast > $dark-contrast {
    @return $light-color;
  } @else {
    @return $dark-color;
  }
}

// 可访问的按钮变体
@mixin accessible-button-variant($bg-color, $border-color: $bg-color) {
  $text-color: auto-text-color($bg-color);
  $hover-bg: darken($bg-color, 7.5%);
  $hover-border: darken($border-color, 10%);
  $active-bg: darken($bg-color, 10%);
  $active-border: darken($border-color, 12.5%);
  
  color: $text-color;
  background-color: $bg-color;
  border-color: $border-color;
  
  &:hover {
    color: auto-text-color($hover-bg);
    background-color: $hover-bg;
    border-color: $hover-border;
  }
  
  &:focus,
  &.focus {
    color: auto-text-color($hover-bg);
    background-color: $hover-bg;
    border-color: $hover-border;
    box-shadow: 0 0 0 0.2rem rgba($bg-color, 0.5);
  }
  
  &:active,
  &.active {
    color: auto-text-color($active-bg);
    background-color: $active-bg;
    border-color: $active-border;
  }
  
  &:disabled,
  &.disabled {
    opacity: 0.65;
  }
}

// 可访问的警告框变体
@mixin accessible-alert-variant($bg-color, $border-color, $text-color) {
  $contrast-ratio: contrast-ratio($bg-color, $text-color);
  
  @if $contrast-ratio < 4.5 {
    @warn "Warning: Contrast ratio #{$contrast-ratio} is below WCAG AA standard (4.5:1)";
  }
  
  background-color: $bg-color;
  border-color: $border-color;
  color: $text-color;
  
  .alert-link {
    color: darken($text-color, 10%);
    
    &:hover {
      color: darken($text-color, 20%);
    }
  }
}

// 可访问的表单控件变体
@mixin accessible-form-control-variant($border-color, $focus-color) {
  border-color: $border-color;
  
  &:focus {
    border-color: $focus-color;
    box-shadow: 0 0 0 0.2rem rgba($focus-color, 0.25);
    outline: 2px solid transparent;
    outline-offset: 2px;
  }
  
  &.is-valid {
    border-color: $success;
    
    &:focus {
      border-color: $success;
      box-shadow: 0 0 0 0.2rem rgba($success, 0.25);
    }
  }
  
  &.is-invalid {
    border-color: $danger;
    
    &:focus {
      border-color: $danger;
      box-shadow: 0 0 0 0.2rem rgba($danger, 0.25);
    }
  }
}

// 使用示例
.btn-accessible-primary {
  @include accessible-button-variant($primary);
}

.btn-accessible-warning {
  @include accessible-button-variant($warning-dark);
}

.alert-accessible-info {
  @include accessible-alert-variant(lighten($info, 45%), $info, $info-dark);
}

.form-control-accessible {
  @include accessible-form-control-variant($gray-300, $primary);
}

3. 视觉设计的可访问性原则

<!-- 视觉设计可访问性示例 -->
<div class="container mt-4">
    <h2>视觉设计可访问性</h2>
    
    <!-- 字体大小和行高 -->
    <section class="mb-5">
        <h3>字体大小和行高</h3>
        
        <div class="row">
            <div class="col-md-6">
                <h4>推荐的字体大小</h4>
                <div class="p-3 border">
                    <p style="font-size: 16px; line-height: 1.5;">正文文字 (16px, 行高 1.5)</p>
                    <p style="font-size: 14px; line-height: 1.4;">小号文字 (14px, 行高 1.4)</p>
                    <p style="font-size: 12px; line-height: 1.3;">最小文字 (12px, 行高 1.3)</p>
                </div>
            </div>
            
            <div class="col-md-6">
                <h4>避免的字体大小</h4>
                <div class="p-3 border bg-light">
                    <p style="font-size: 10px; line-height: 1.2;" class="text-muted">过小的文字 (10px) - 难以阅读</p>
                    <p style="font-size: 16px; line-height: 1.0;" class="text-muted">行高过小 (1.0) - 行间距不足</p>
                </div>
            </div>
        </div>
    </section>
    
    <!-- 图标和文字组合 -->
    <section class="mb-5">
        <h3>图标和文字组合</h3>
        
        <div class="row">
            <div class="col-md-6">
                <h4>良好的图标使用</h4>
                <div class="list-group">
                    <div class="list-group-item">
                        <i class="fas fa-check-circle text-success me-2" aria-hidden="true"></i>
                        <span>成功状态 (图标 + 文字)</span>
                    </div>
                    <div class="list-group-item">
                        <i class="fas fa-exclamation-triangle text-warning me-2" aria-hidden="true"></i>
                        <span>警告状态 (图标 + 文字)</span>
                    </div>
                    <div class="list-group-item">
                        <i class="fas fa-times-circle text-danger me-2" aria-hidden="true"></i>
                        <span>错误状态 (图标 + 文字)</span>
                    </div>
                </div>
            </div>
            
            <div class="col-md-6">
                <h4>避免仅使用图标</h4>
                <div class="list-group">
                    <div class="list-group-item bg-light">
                        <i class="fas fa-check-circle text-success" aria-hidden="true"></i>
                        <span class="text-muted ms-2">(仅图标,含义不明确)</span>
                    </div>
                    <div class="list-group-item bg-light">
                        <i class="fas fa-exclamation-triangle text-warning" aria-hidden="true"></i>
                        <span class="text-muted ms-2">(仅图标,含义不明确)</span>
                    </div>
                    <div class="list-group-item bg-light">
                        <i class="fas fa-times-circle text-danger" aria-hidden="true"></i>
                        <span class="text-muted ms-2">(仅图标,含义不明确)</span>
                    </div>
                </div>
            </div>
        </div>
    </section>
    
    <!-- 焦点指示器 -->
    <section class="mb-5">
        <h3>焦点指示器</h3>
        
        <div class="row">
            <div class="col-md-6">
                <h4>清晰的焦点指示器</h4>
                <div class="d-grid gap-2">
                    <button class="btn btn-primary focus-visible-example">主要按钮</button>
                    <button class="btn btn-outline-secondary focus-visible-example">次要按钮</button>
                    <a href="#" class="btn btn-link focus-visible-example">链接按钮</a>
                </div>
            </div>
            
            <div class="col-md-6">
                <h4>表单控件焦点</h4>
                <form>
                    <div class="mb-3">
                        <label for="focusInput1" class="form-label">文本输入</label>
                        <input type="text" class="form-control focus-visible-example" id="focusInput1">
                    </div>
                    <div class="mb-3">
                        <label for="focusSelect1" class="form-label">选择框</label>
                        <select class="form-select focus-visible-example" id="focusSelect1">
                            <option>选项 1</option>
                            <option>选项 2</option>
                        </select>
                    </div>
                    <div class="form-check">
                        <input class="form-check-input focus-visible-example" type="checkbox" id="focusCheck1">
                        <label class="form-check-label" for="focusCheck1">
                            复选框
                        </label>
                    </div>
                </form>
            </div>
        </div>
    </section>
    
    <!-- 间距和布局 -->
    <section class="mb-5">
        <h3>间距和布局</h3>
        
        <div class="row">
            <div class="col-md-6">
                <h4>适当的间距</h4>
                <div class="card">
                    <div class="card-body">
                        <h5 class="card-title mb-3">卡片标题</h5>
                        <p class="card-text mb-3">这是卡片的内容,具有适当的间距。</p>
                        <div class="d-grid gap-2 d-md-flex">
                            <button class="btn btn-primary me-md-2">主要操作</button>
                            <button class="btn btn-outline-secondary">次要操作</button>
                        </div>
                    </div>
                </div>
            </div>
            
            <div class="col-md-6">
                <h4>触摸目标大小</h4>
                <div class="d-grid gap-3">
                    <button class="btn btn-lg btn-primary">大按钮 (推荐移动端)</button>
                    <button class="btn btn-primary">标准按钮</button>
                    <button class="btn btn-sm btn-primary">小按钮 (避免在移动端使用)</button>
                </div>
                <div class="mt-3">
                    <small class="text-muted">
                        移动端触摸目标应至少为 44x44 像素
                    </small>
                </div>
            </div>
        </div>
    </section>
</div>

<style>
/* 焦点指示器样式 */
.focus-visible-example:focus {
    outline: 2px solid #0d6efd;
    outline-offset: 2px;
    box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}

/* 高对比度模式支持 */
@media (prefers-contrast: high) {
    .focus-visible-example:focus {
        outline: 3px solid;
        outline-offset: 2px;
    }
}

/* 减少动画偏好 */
@media (prefers-reduced-motion: reduce) {
    * {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}
</style>

屏幕阅读器支持

1. 屏幕阅读器优化

<!-- 屏幕阅读器优化示例 -->
<div class="container mt-4">
    <h2>屏幕阅读器支持</h2>
    
    <!-- 跳转链接 -->
    <div class="skip-links">
        <a href="#main-content" class="skip-link">跳转到主要内容</a>
        <a href="#navigation" class="skip-link">跳转到导航</a>
        <a href="#sidebar" class="skip-link">跳转到侧边栏</a>
    </div>
    
    <!-- 页面结构 -->
    <header>
        <nav id="navigation" class="navbar navbar-expand-lg navbar-dark bg-primary" aria-label="主导航">
            <div class="container">
                <a class="navbar-brand" href="#">
                    <img src="logo.png" alt="公司标志" width="30" height="30">
                    公司名称
                </a>
                
                <button class="navbar-toggler" 
                        type="button" 
                        data-bs-toggle="collapse" 
                        data-bs-target="#navbarNav" 
                        aria-controls="navbarNav" 
                        aria-expanded="false" 
                        aria-label="切换导航菜单">
                    <span class="navbar-toggler-icon"></span>
                </button>
                
                <div class="collapse navbar-collapse" id="navbarNav">
                    <ul class="navbar-nav ms-auto">
                        <li class="nav-item">
                            <a class="nav-link active" 
                               aria-current="page" 
                               href="#">首页</a>
                        </li>
                        <li class="nav-item dropdown">
                            <a class="nav-link dropdown-toggle" 
                               href="#" 
                               id="servicesDropdown" 
                               role="button" 
                               data-bs-toggle="dropdown" 
                               aria-expanded="false">
                                服务
                            </a>
                            <ul class="dropdown-menu" aria-labelledby="servicesDropdown">
                                <li><a class="dropdown-item" href="#">网站开发</a></li>
                                <li><a class="dropdown-item" href="#">移动应用</a></li>
                                <li><hr class="dropdown-divider"></li>
                                <li><a class="dropdown-item" href="#">咨询服务</a></li>
                            </ul>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">关于我们</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">联系我们</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    
    <div class="row mt-4">
        <aside id="sidebar" class="col-md-3">
            <nav aria-label="侧边栏导航">
                <h3>快速导航</h3>
                <ul class="list-unstyled">
                    <li><a href="#section1">第一部分</a></li>
                    <li><a href="#section2">第二部分</a></li>
                    <li><a href="#section3">第三部分</a></li>
                </ul>
            </nav>
            
            <!-- 搜索功能 -->
            <div class="mt-4">
                <h3>搜索</h3>
                <form role="search">
                    <div class="input-group">
                        <label for="search-input" class="visually-hidden">搜索内容</label>
                        <input type="search" 
                               class="form-control" 
                               id="search-input" 
                               placeholder="搜索..." 
                               aria-describedby="search-help">
                        <button class="btn btn-outline-secondary" 
                                type="submit" 
                                aria-label="执行搜索">
                            <i class="fas fa-search" aria-hidden="true"></i>
                        </button>
                    </div>
                    <div id="search-help" class="form-text">
                        输入关键词搜索相关内容
                    </div>
                </form>
            </div>
        </aside>
        
        <main id="main-content" class="col-md-9">
            <h1>页面主标题</h1>
            
            <!-- 面包屑导航 -->
            <nav aria-label="面包屑导航">
                <ol class="breadcrumb">
                    <li class="breadcrumb-item"><a href="#">首页</a></li>
                    <li class="breadcrumb-item"><a href="#">产品</a></li>
                    <li class="breadcrumb-item active" aria-current="page">详情页</li>
                </ol>
            </nav>
            
            <!-- 内容区域 -->
            <section id="section1" aria-labelledby="section1-heading">
                <h2 id="section1-heading">第一部分</h2>
                <p>这是第一部分的内容。屏幕阅读器会读取这些文字。</p>
                
                <!-- 数据表格 -->
                <table class="table table-striped" aria-label="产品信息表">
                    <caption>产品价格和库存信息</caption>
                    <thead>
                        <tr>
                            <th scope="col">产品名称</th>
                            <th scope="col">价格</th>
                            <th scope="col">库存</th>
                            <th scope="col">状态</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <th scope="row">笔记本电脑</th>
                            <td>¥5,999</td>
                            <td>15</td>
                            <td>
                                <span class="badge bg-success">有库存</span>
                                <span class="visually-hidden">,库存充足</span>
                            </td>
                        </tr>
                        <tr>
                            <th scope="row">智能手机</th>
                            <td>¥2,999</td>
                            <td>3</td>
                            <td>
                                <span class="badge bg-warning text-dark">库存不足</span>
                                <span class="visually-hidden">,仅剩3件</span>
                            </td>
                        </tr>
                        <tr>
                            <th scope="row">平板电脑</th>
                            <td>¥1,999</td>
                            <td>0</td>
                            <td>
                                <span class="badge bg-danger">缺货</span>
                                <span class="visually-hidden">,暂时缺货</span>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </section>
            
            <section id="section2" aria-labelledby="section2-heading">
                <h2 id="section2-heading">第二部分</h2>
                
                <!-- 图片和替代文本 -->
                <figure>
                    <img src="chart.png" 
                         class="img-fluid" 
                         alt="2024年销售数据图表,显示第一季度销售额为100万,第二季度为120万,第三季度为150万,第四季度预计为180万">
                    <figcaption>2024年销售数据趋势图</figcaption>
                </figure>
                
                <!-- 复杂图表的文字描述 -->
                <div class="visually-hidden">
                    <h3>图表详细描述</h3>
                    <p>该图表显示了2024年各季度的销售数据:</p>
                    <ul>
                        <li>第一季度:100万元</li>
                        <li>第二季度:120万元,增长20%</li>
                        <li>第三季度:150万元,增长25%</li>
                        <li>第四季度:预计180万元,预期增长20%</li>
                    </ul>
                    <p>总体趋势显示销售额稳步增长。</p>
                </div>
            </section>
            
            <section id="section3" aria-labelledby="section3-heading">
                <h2 id="section3-heading">第三部分</h2>
                
                <!-- 交互式内容 -->
                <div class="accordion" id="accessibleAccordion">
                    <div class="accordion-item">
                        <h3 class="accordion-header" id="headingOne">
                            <button class="accordion-button" 
                                    type="button" 
                                    data-bs-toggle="collapse" 
                                    data-bs-target="#collapseOne" 
                                    aria-expanded="true" 
                                    aria-controls="collapseOne">
                                常见问题 1
                            </button>
                        </h3>
                        <div id="collapseOne" 
                             class="accordion-collapse collapse show" 
                             aria-labelledby="headingOne" 
                             data-bs-parent="#accessibleAccordion">
                            <div class="accordion-body">
                                这是第一个常见问题的答案。屏幕阅读器用户可以通过键盘导航来展开和折叠这些内容。
                            </div>
                        </div>
                    </div>
                    
                    <div class="accordion-item">
                        <h3 class="accordion-header" id="headingTwo">
                            <button class="accordion-button collapsed" 
                                    type="button" 
                                    data-bs-toggle="collapse" 
                                    data-bs-target="#collapseTwo" 
                                    aria-expanded="false" 
                                    aria-controls="collapseTwo">
                                常见问题 2
                            </button>
                        </h3>
                        <div id="collapseTwo" 
                             class="accordion-collapse collapse" 
                             aria-labelledby="headingTwo" 
                             data-bs-parent="#accessibleAccordion">
                            <div class="accordion-body">
                                这是第二个常见问题的答案。
                            </div>
                        </div>
                    </div>
                </div>
            </section>
        </main>
    </div>
</div>

<style>
/* 跳转链接样式 */
.skip-links {
    position: absolute;
    top: -40px;
    left: 6px;
    z-index: 1000;
}

.skip-link {
    position: absolute;
    top: -40px;
    left: 6px;
    background: #000;
    color: #fff;
    padding: 8px;
    text-decoration: none;
    border-radius: 0 0 4px 4px;
    transition: top 0.3s;
}

.skip-link:focus {
    top: 0;
    color: #fff;
}

/* 屏幕阅读器专用内容 */
.visually-hidden {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

/* 仅在获得焦点时显示 */
.visually-hidden-focusable:not(:focus):not(:focus-within) {
    @extend .visually-hidden;
}
</style>

2. 动态内容更新

// screen-reader-support.js
// 屏幕阅读器支持管理器

class ScreenReaderSupport {
  constructor() {
    this.liveRegions = new Map();
    this.init();
  }
  
  // 初始化屏幕阅读器支持
  init() {
    this.createLiveRegions();
    this.setupFormValidation();
    this.setupDynamicContent();
    this.setupProgressAnnouncements();
  }
  
  // 创建实时区域
  createLiveRegions() {
    // 创建礼貌的实时区域
    const politeRegion = document.createElement('div');
    politeRegion.id = 'polite-announcements';
    politeRegion.setAttribute('aria-live', 'polite');
    politeRegion.setAttribute('aria-atomic', 'true');
    politeRegion.className = 'visually-hidden';
    document.body.appendChild(politeRegion);
    this.liveRegions.set('polite', politeRegion);
    
    // 创建断言的实时区域
    const assertiveRegion = document.createElement('div');
    assertiveRegion.id = 'assertive-announcements';
    assertiveRegion.setAttribute('aria-live', 'assertive');
    assertiveRegion.setAttribute('aria-atomic', 'true');
    assertiveRegion.className = 'visually-hidden';
    document.body.appendChild(assertiveRegion);
    this.liveRegions.set('assertive', assertiveRegion);
    
    // 创建状态区域
    const statusRegion = document.createElement('div');
    statusRegion.id = 'status-announcements';
    statusRegion.setAttribute('role', 'status');
    statusRegion.setAttribute('aria-atomic', 'true');
    statusRegion.className = 'visually-hidden';
    document.body.appendChild(statusRegion);
    this.liveRegions.set('status', statusRegion);
  }
  
  // 宣布消息
  announce(message, priority = 'polite', delay = 100) {
    const region = this.liveRegions.get(priority);
    if (!region) {
      console.warn(`Live region '${priority}' not found`);
      return;
    }
    
    // 清除之前的内容
    region.textContent = '';
    
    // 延迟添加新内容,确保屏幕阅读器能够检测到变化
    setTimeout(() => {
      region.textContent = message;
    }, delay);
    
    // 自动清除消息
    setTimeout(() => {
      if (region.textContent === message) {
        region.textContent = '';
      }
    }, 5000);
  }
  
  // 设置表单验证
  setupFormValidation() {
    document.addEventListener('invalid', (e) => {
      const field = e.target;
      const label = this.getFieldLabel(field);
      const errorMessage = this.getValidationMessage(field);
      
      // 设置 aria-invalid
      field.setAttribute('aria-invalid', 'true');
      
      // 宣布错误
      this.announce(`${label}: ${errorMessage}`, 'assertive');
      
      // 添加错误描述
      this.addErrorDescription(field, errorMessage);
    }, true);
    
    document.addEventListener('input', (e) => {
      const field = e.target;
      if (field.hasAttribute('aria-invalid') && field.checkValidity()) {
        field.setAttribute('aria-invalid', 'false');
        this.removeErrorDescription(field);
      }
    });
  }
  
  // 获取字段标签
  getFieldLabel(field) {
    const label = document.querySelector(`label[for="${field.id}"]`);
    if (label) {
      return label.textContent.trim();
    }
    
    const ariaLabel = field.getAttribute('aria-label');
    if (ariaLabel) {
      return ariaLabel;
    }
    
    const placeholder = field.getAttribute('placeholder');
    if (placeholder) {
      return placeholder;
    }
    
    return '字段';
  }
  
  // 获取验证消息
  getValidationMessage(field) {
    if (field.validity.valueMissing) {
      return '此字段为必填项';
    }
    if (field.validity.typeMismatch) {
      return '请输入有效的格式';
    }
    if (field.validity.patternMismatch) {
      return '输入格式不正确';
    }
    if (field.validity.tooShort) {
      return `至少需要 ${field.minLength} 个字符`;
    }
    if (field.validity.tooLong) {
      return `最多允许 ${field.maxLength} 个字符`;
    }
    if (field.validity.rangeUnderflow) {
      return `值必须大于或等于 ${field.min}`;
    }
    if (field.validity.rangeOverflow) {
      return `值必须小于或等于 ${field.max}`;
    }
    
    return field.validationMessage || '输入无效';
  }
  
  // 添加错误描述
  addErrorDescription(field, message) {
    let errorId = field.getAttribute('aria-describedby');
    let errorElement = errorId ? document.getElementById(errorId) : null;
    
    if (!errorElement) {
      errorId = `${field.id}-error`;
      errorElement = document.createElement('div');
      errorElement.id = errorId;
      errorElement.className = 'invalid-feedback';
      errorElement.setAttribute('role', 'alert');
      
      field.parentNode.appendChild(errorElement);
      field.setAttribute('aria-describedby', errorId);
    }
    
    errorElement.textContent = message;
    errorElement.style.display = 'block';
  }
  
  // 移除错误描述
  removeErrorDescription(field) {
    const errorId = field.getAttribute('aria-describedby');
    if (errorId) {
      const errorElement = document.getElementById(errorId);
      if (errorElement && errorElement.classList.contains('invalid-feedback')) {
        errorElement.style.display = 'none';
      }
    }
  }
  
  // 设置动态内容
  setupDynamicContent() {
    // 监听 Bootstrap 组件事件
    document.addEventListener('shown.bs.modal', (e) => {
      const modal = e.target;
      const title = modal.querySelector('.modal-title');
      if (title) {
        this.announce(`对话框已打开: ${title.textContent}`, 'polite');
      }
    });
    
    document.addEventListener('hidden.bs.modal', (e) => {
      this.announce('对话框已关闭', 'polite');
    });
    
    document.addEventListener('shown.bs.collapse', (e) => {
      const trigger = document.querySelector(`[data-bs-target="#${e.target.id}"]`);
      if (trigger) {
        this.announce(`${trigger.textContent} 已展开`, 'polite');
      }
    });
    
    document.addEventListener('hidden.bs.collapse', (e) => {
      const trigger = document.querySelector(`[data-bs-target="#${e.target.id}"]`);
      if (trigger) {
        this.announce(`${trigger.textContent} 已折叠`, 'polite');
      }
    });
    
    // 监听标签页切换
    document.addEventListener('shown.bs.tab', (e) => {
      const tab = e.target;
      this.announce(`已切换到 ${tab.textContent} 标签页`, 'polite');
    });
  }
  
  // 设置进度宣布
  setupProgressAnnouncements() {
    // 监听进度条更新
    const progressBars = document.querySelectorAll('.progress-bar');
    progressBars.forEach(bar => {
      const observer = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
          if (mutation.type === 'attributes' && mutation.attributeName === 'aria-valuenow') {
            const value = bar.getAttribute('aria-valuenow');
            const max = bar.getAttribute('aria-valuemax') || 100;
            const percentage = Math.round((value / max) * 100);
            this.announce(`进度: ${percentage}%`, 'polite');
          }
        });
      });
      
      observer.observe(bar, {
        attributes: true,
        attributeFilter: ['aria-valuenow']
      });
    });
  }
  
  // 宣布页面变化
  announcePageChange(title, description) {
    this.announce(`页面已更改: ${title}. ${description}`, 'polite');
  }
  
  // 宣布搜索结果
  announceSearchResults(count, query) {
    if (count === 0) {
      this.announce(`没有找到 "${query}" 的搜索结果`, 'polite');
    } else {
      this.announce(`找到 ${count} 个 "${query}" 的搜索结果`, 'polite');
    }
  }
  
  // 宣布加载状态
  announceLoading(message = '正在加载') {
    this.announce(message, 'polite');
  }
  
  // 宣布完成状态
  announceComplete(message = '加载完成') {
    this.announce(message, 'polite');
  }
  
  // 宣布错误
  announceError(message) {
    this.announce(`错误: ${message}`, 'assertive');
  }
  
  // 宣布成功
  announceSuccess(message) {
    this.announce(`成功: ${message}`, 'polite');
  }
}

// 创建全局实例
const screenReaderSupport = new ScreenReaderSupport();

// 导出
export default screenReaderSupport;

3. 可访问的表单验证

<!-- 可访问的表单验证示例 -->
<div class="container mt-4">
    <h2>可访问的表单验证</h2>
    
    <form id="accessibleForm" novalidate>
        <fieldset>
            <legend>用户注册表单</legend>
            
            <!-- 用户名字段 -->
            <div class="mb-3">
                <label for="username" class="form-label">
                    用户名 <span class="text-danger" aria-label="必填">*</span>
                </label>
                <input type="text" 
                       class="form-control" 
                       id="username" 
                       name="username"
                       required 
                       minlength="3" 
                       maxlength="20"
                       pattern="[a-zA-Z0-9_]+"
                       aria-required="true"
                       aria-describedby="username-help username-error">
                <div id="username-help" class="form-text">
                    用户名必须为3-20个字符,只能包含字母、数字和下划线
                </div>
                <div id="username-error" class="invalid-feedback" role="alert" style="display: none;"></div>
            </div>
            
            <!-- 邮箱字段 -->
            <div class="mb-3">
                <label for="email" class="form-label">
                    邮箱地址 <span class="text-danger" aria-label="必填">*</span>
                </label>
                <input type="email" 
                       class="form-control" 
                       id="email" 
                       name="email"
                       required 
                       aria-required="true"
                       aria-describedby="email-help email-error">
                <div id="email-help" class="form-text">
                    请输入有效的邮箱地址
                </div>
                <div id="email-error" class="invalid-feedback" role="alert" style="display: none;"></div>
            </div>
            
            <!-- 密码字段 -->
            <div class="mb-3">
                <label for="password" class="form-label">
                    密码 <span class="text-danger" aria-label="必填">*</span>
                </label>
                <div class="input-group">
                    <input type="password" 
                           class="form-control" 
                           id="password" 
                           name="password"
                           required 
                           minlength="8"
                           aria-required="true"
                           aria-describedby="password-help password-error">
                    <button class="btn btn-outline-secondary" 
                            type="button" 
                            id="togglePassword"
                            aria-label="显示密码">
                        <i class="fas fa-eye" aria-hidden="true"></i>
                    </button>
                </div>
                <div id="password-help" class="form-text">
                    密码必须至少8个字符,包含大小写字母、数字和特殊字符
                </div>
                <div id="password-error" class="invalid-feedback" role="alert" style="display: none;"></div>
                
                <!-- 密码强度指示器 -->
                <div class="mt-2">
                    <div class="progress" style="height: 5px;">
                        <div id="password-strength" 
                             class="progress-bar" 
                             role="progressbar" 
                             aria-valuenow="0" 
                             aria-valuemin="0" 
                             aria-valuemax="100"
                             aria-label="密码强度">
                        </div>
                    </div>
                    <small id="password-strength-text" class="form-text">密码强度: 无</small>
                </div>
            </div>
            
            <!-- 确认密码字段 -->
            <div class="mb-3">
                <label for="confirmPassword" class="form-label">
                    确认密码 <span class="text-danger" aria-label="必填">*</span>
                </label>
                <input type="password" 
                       class="form-control" 
                       id="confirmPassword" 
                       name="confirmPassword"
                       required 
                       aria-required="true"
                       aria-describedby="confirm-password-help confirm-password-error">
                <div id="confirm-password-help" class="form-text">
                    请再次输入密码以确认
                </div>
                <div id="confirm-password-error" class="invalid-feedback" role="alert" style="display: none;"></div>
            </div>
            
            <!-- 生日字段 -->
            <div class="mb-3">
                <label for="birthdate" class="form-label">出生日期</label>
                <input type="date" 
                       class="form-control" 
                       id="birthdate" 
                       name="birthdate"
                       max="2006-01-01"
                       aria-describedby="birthdate-help birthdate-error">
                <div id="birthdate-help" class="form-text">
                    必须年满18岁才能注册
                </div>
                <div id="birthdate-error" class="invalid-feedback" role="alert" style="display: none;"></div>
            </div>
            
            <!-- 性别选择 -->
            <fieldset class="mb-3">
                <legend class="form-label">性别</legend>
                <div class="form-check">
                    <input class="form-check-input" 
                           type="radio" 
                           name="gender" 
                           id="gender-male" 
                           value="male">
                    <label class="form-check-label" for="gender-male">
                        男性
                    </label>
                </div>
                <div class="form-check">
                    <input class="form-check-input" 
                           type="radio" 
                           name="gender" 
                           id="gender-female" 
                           value="female">
                    <label class="form-check-label" for="gender-female">
                        女性
                    </label>
                </div>
                <div class="form-check">
                    <input class="form-check-input" 
                           type="radio" 
                           name="gender" 
                           id="gender-other" 
                           value="other">
                    <label class="form-check-label" for="gender-other">
                        其他
                    </label>
                </div>
            </fieldset>
            
            <!-- 兴趣爱好 -->
            <fieldset class="mb-3">
                <legend class="form-label">兴趣爱好 (可多选)</legend>
                <div class="form-check">
                    <input class="form-check-input" 
                           type="checkbox" 
                           id="interest-tech" 
                           name="interests" 
                           value="technology">
                    <label class="form-check-label" for="interest-tech">
                        科技
                    </label>
                </div>
                <div class="form-check">
                    <input class="form-check-input" 
                           type="checkbox" 
                           id="interest-sports" 
                           name="interests" 
                           value="sports">
                    <label class="form-check-label" for="interest-sports">
                        体育
                    </label>
                </div>
                <div class="form-check">
                    <input class="form-check-input" 
                           type="checkbox" 
                           id="interest-music" 
                           name="interests" 
                           value="music">
                    <label class="form-check-label" for="interest-music">
                        音乐
                    </label>
                </div>
            </fieldset>
            
            <!-- 同意条款 -->
            <div class="mb-3">
                <div class="form-check">
                    <input class="form-check-input" 
                           type="checkbox" 
                           id="terms" 
                           name="terms" 
                           required 
                           aria-required="true"
                           aria-describedby="terms-error">
                    <label class="form-check-label" for="terms">
                        我同意 <a href="#" target="_blank">服务条款</a> 和 <a href="#" target="_blank">隐私政策</a>
                        <span class="text-danger" aria-label="必填">*</span>
                    </label>
                    <div id="terms-error" class="invalid-feedback" role="alert" style="display: none;"></div>
                </div>
            </div>
            
            <!-- 提交按钮 -->
            <div class="d-grid gap-2 d-md-flex justify-content-md-end">
                <button type="reset" class="btn btn-outline-secondary me-md-2">重置</button>
                <button type="submit" class="btn btn-primary">
                    <span id="submit-text">注册</span>
                    <span id="submit-loading" class="spinner-border spinner-border-sm ms-2" 
                          role="status" aria-hidden="true" style="display: none;"></span>
                </button>
            </div>
        </fieldset>
    </form>
    
    <!-- 提交结果 -->
    <div id="form-result" class="mt-3" role="alert" aria-live="polite" style="display: none;"></div>
</div>

<script>
// 可访问的表单验证脚本
document.addEventListener('DOMContentLoaded', function() {
    const form = document.getElementById('accessibleForm');
    const passwordField = document.getElementById('password');
    const confirmPasswordField = document.getElementById('confirmPassword');
    const togglePasswordBtn = document.getElementById('togglePassword');
    const passwordStrengthBar = document.getElementById('password-strength');
    const passwordStrengthText = document.getElementById('password-strength-text');
    
    // 密码显示/隐藏切换
    togglePasswordBtn.addEventListener('click', function() {
        const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
        passwordField.setAttribute('type', type);
        
        const icon = this.querySelector('i');
        const label = type === 'password' ? '显示密码' : '隐藏密码';
        
        icon.className = type === 'password' ? 'fas fa-eye' : 'fas fa-eye-slash';
        this.setAttribute('aria-label', label);
        
        // 宣布状态变化
        screenReaderSupport.announce(label, 'polite');
    });
    
    // 密码强度检测
    passwordField.addEventListener('input', function() {
        const password = this.value;
        const strength = calculatePasswordStrength(password);
        
        updatePasswordStrength(strength);
    });
    
    // 确认密码验证
    confirmPasswordField.addEventListener('input', function() {
        validatePasswordMatch();
    });
    
    passwordField.addEventListener('input', function() {
        if (confirmPasswordField.value) {
            validatePasswordMatch();
        }
    });
    
    // 表单提交
    form.addEventListener('submit', function(e) {
        e.preventDefault();
        
        if (this.checkValidity() && validatePasswordMatch()) {
            submitForm();
        } else {
            // 聚焦到第一个错误字段
            const firstError = this.querySelector('.is-invalid, :invalid');
            if (firstError) {
                firstError.focus();
                screenReaderSupport.announce('表单包含错误,请检查并修正', 'assertive');
            }
        }
    });
    
    // 计算密码强度
    function calculatePasswordStrength(password) {
        let strength = 0;
        
        if (password.length >= 8) strength += 25;
        if (/[a-z]/.test(password)) strength += 25;
        if (/[A-Z]/.test(password)) strength += 25;
        if (/[0-9]/.test(password)) strength += 15;
        if (/[^a-zA-Z0-9]/.test(password)) strength += 10;
        
        return Math.min(strength, 100);
    }
    
    // 更新密码强度显示
    function updatePasswordStrength(strength) {
        passwordStrengthBar.style.width = strength + '%';
        passwordStrengthBar.setAttribute('aria-valuenow', strength);
        
        let strengthText = '无';
        let strengthClass = '';
        
        if (strength >= 80) {
            strengthText = '强';
            strengthClass = 'bg-success';
        } else if (strength >= 60) {
            strengthText = '中等';
            strengthClass = 'bg-info';
        } else if (strength >= 40) {
            strengthText = '弱';
            strengthClass = 'bg-warning';
        } else if (strength > 0) {
            strengthText = '很弱';
            strengthClass = 'bg-danger';
        }
        
        passwordStrengthBar.className = `progress-bar ${strengthClass}`;
        passwordStrengthText.textContent = `密码强度: ${strengthText}`;
    }
    
    // 验证密码匹配
    function validatePasswordMatch() {
        const password = passwordField.value;
        const confirmPassword = confirmPasswordField.value;
        
        if (confirmPassword && password !== confirmPassword) {
            confirmPasswordField.setCustomValidity('密码不匹配');
            confirmPasswordField.classList.add('is-invalid');
            confirmPasswordField.setAttribute('aria-invalid', 'true');
            
            const errorElement = document.getElementById('confirm-password-error');
            errorElement.textContent = '密码不匹配';
            errorElement.style.display = 'block';
            
            return false;
        } else {
            confirmPasswordField.setCustomValidity('');
            confirmPasswordField.classList.remove('is-invalid');
            confirmPasswordField.setAttribute('aria-invalid', 'false');
            
            const errorElement = document.getElementById('confirm-password-error');
            errorElement.style.display = 'none';
            
            return true;
        }
    }
    
    // 提交表单
    function submitForm() {
        const submitBtn = form.querySelector('button[type="submit"]');
        const submitText = document.getElementById('submit-text');
        const submitLoading = document.getElementById('submit-loading');
        const resultDiv = document.getElementById('form-result');
        
        // 显示加载状态
        submitBtn.disabled = true;
        submitText.textContent = '提交中...';
        submitLoading.style.display = 'inline-block';
        
        screenReaderSupport.announceLoading('正在提交表单');
        
        // 模拟提交过程
        setTimeout(() => {
            // 恢复按钮状态
            submitBtn.disabled = false;
            submitText.textContent = '注册';
            submitLoading.style.display = 'none';
            
            // 显示成功消息
            resultDiv.className = 'alert alert-success mt-3';
            resultDiv.textContent = '注册成功!欢迎加入我们。';
            resultDiv.style.display = 'block';
            
            screenReaderSupport.announceSuccess('注册成功');
            
            // 重置表单
            form.reset();
            updatePasswordStrength(0);
        }, 2000);
    }
});
</script>