🛡️
🛡️

WCAG 2.2 标准落地

  • 焦点可见性与目标大小(24x24px / 44x44px)的合规实践。

  • 确保焦点不被遮挡,为所有用户提供公平的访问体验。

Slide 1 of 3Remaining 2

2026 年,Web Accessibility 新时代来临

2026 年是可访问性不再是努力目标而是法律义务的一年。WCAG 2.2 将作为 ISO/IEC 40500:2026 正式标准化,华盛顿州也将在 2026 年 7 月 1 日起强制要求 WCAG 2.2 AA 合规。

本文将以完整代码,讲解我在生产环境中实际落地的 Astro 博客可访问性与 PWA 改进。你将以可直接实作的方式学习 WCAG 2.2 的 9 项新增 Success Criteria,以及基于 Workbox 的最新 PWA 策略。

引用: YouTube

为什么现在是 WCAG 2.2?

⚠️

法律压力上升 :2026 年各国可访问性法规持续强化。美国 ADA 网站诉讼激增、欧洲无障碍法案(European Accessibility Act)全面实施,导致合规风险达到历史高点。

WCAG 2.2 新增的 9 项 Success Criteria

WCAG 2.2 已在 2023 年 10 月 5 日发布,但 预计在 2026 年下半年以 ISO/IEC 40500:2026 正式采纳 。新增的 9 项成功准则如下:

Success Criteria 等级 重点领域
2.4.11 焦点不被遮挡(最低) AA 键盘焦点不会被完全遮挡
2.4.12 焦点不被遮挡(增强) AAA 焦点指示器完全可见
2.4.13 焦点可见性, AAA, 2px 宽度 + 3:1 对比度
2.5.7 拖拽操作, AA, 提供拖拽操作的替代方式
2.5.8 目标大小(最低), AA, 最小 24x24px(推荐 44x44px)
3.2.6 一致的帮助, A, 帮助功能放置一致
3.3.7 冗余输入, A, 复用已输入信息(自动填充)
3.3.8 可访问的认证(最低) AA 降低认知负担的认证方式
3.3.9 可访问的认证(增强) AAA 进一步强化认证可访问性

被删除的条目 :4.1.1 Parsing(在 HTML5 中已废弃)

引用: YouTube

第 1 节:焦点管理实现

1.1 使用 :focus-visible 的高对比度焦点环

要满足 WCAG 2.4.13 焦点可见性 (AAA),需要至少 2px 宽度与 3:1 对比度。我为所有可交互元素应用了统一的焦点样式。

src/styles/ux.css
/* Focus Indicators (WCAG 2.4.13 AAA) */
:focus-visible {
 outline: 2px solid var(--color-primary, #3b82f6);
 outline-offset: 2px;
 border-radius: 4px;
}

为什么用 :focus-visible 而不是 :focus

  • :focus: 鼠标点击时也显示焦点环(用户体验更杂乱)
  • :focus-visible: 仅在键盘导航时显示(更优)

1.2 Skip to Content 链接(2.4.1 Bypass Blocks AA)

为键盘用户提供跳过导航直接进入主内容的链接。

src/layouts/BaseLayout.astro
<body>
 {/*  Skip to Content Link  */}
 <a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-[100] bg-primary text-white px-4 py-2 rounded shadow-lg ring-2 ring-white">
 {t("nav.skip_to_content")}
 </a>
 <Header />
 <main id="main-content" class="flex-grow">
 <slot />
</main>
</body>

关键点

  • sr-only: 平时隐藏(屏幕阅读器仍可读取)
  • focus:not-sr-only: 获取焦点时可见
  • id="main-content": 清晰定义跳转目标

1.3 多语言 Skip Link(i18n 支持)

src/i18n/ui.ts
export const ui = {
 ja: {
 "nav.skip_to_content": "メインコンテンツへスキップ",
 },
 en: {
 "nav.skip_to_content": "Skip to main content",
 },
 zh: {
 "nav.skip_to_content": "跳转到主要内容",
 },
};

第 2 节:目标大小合规(2.5.8 AA)

2.1 44x44px 黄金标准

WCAG 2.5.8(Minimum AA)规定最小为24x24px,但 强烈建议采用 WCAG 2.5.5(Enhanced AAA)的 44x44px 。原因:

  1. 防止触屏误触
  2. 兼顾运动障碍用户
  3. 与 iOS 人机界面指南一致

2.2 实现示例:ThemeToggle Button

src/components/ui/ThemeToggle.astro
<button type="button" class="theme-toggle-btn rounded-full flex items-center justify-center min-w-[44px] min-h-[44px]"
 aria-label={t("nav.dark_mode")}
>
 <svg class="w-5 h-5" aria-hidden="true">
 {/*  Icon  */}
</svg>
</button>

技巧 : 图标本身是 w-5 h-5(20px),但通过 min-w/h-[44px] 与 padding 将可点击区域扩展到 44px

2.3 SearchDialog 关闭按钮

src/components/ui/SearchDialog.astro
<button
 id="close-search" class="flex items-center justify-center min-w-[44px] min-h-[44px]"
 aria-label={t("search.close")}
>
 <svg class="w-5 h-5">
 <line x1="18" y1="6" x2="6" y2="18"></line>
 <line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
💡

专业提示 : 使用 min-w/min-h 而非 padding,是因为在 Flexbox 布局中可预测性更强,调试也更容易。你可以用 Chrome DevTools 直观确认 44px 的可点击区域。

第 3 节:ARIA 最佳实践

3.1 交互组件的 ARIA 状态

为满足 WCAG 4.1.2 Name, Role, “Value (A),需要通过 ARIA 属性明确状态。

使用 aria-pressed 的 LikeButton

src/components/ui/LikeButton.astro
<button class="like-button-component"
 data-slug={slug}
 aria-pressed="false"
 aria-label="いいね">
 <span aria-hidden="true">❤️</span>
 <span>いいね</span>
 <span class="like-count">--</span>
</button>

<script>
 function updateButtonAppearance() {
 btn.setAttribute("aria-pressed", String(isLiked)); // true/false
 // Visual update logic...
 }
</script>

为什么在 emoji 上加 aria-hidden="true" 屏幕阅读器会冗余朗读表情符号,因此用 aria-label 明确为“いいね”,而把视觉上的 ❤️ 隐藏。

使用 aria-expanded 的移动端菜单

src/components/layout/Header.astro
<button
 id="menu-toggle"
 aria-label={t("nav.menu")}
 aria-expanded="false">
 <svg>{/*  Hamburger Icon  */}</svg>
</button>

<script>
 function toggleMenu() {
 const isOpen = menu.classList.contains("hidden");
 btn.setAttribute("aria-expanded", String(isOpen));
 menu.classList.toggle("hidden");
 }
</script>

3.2 本地化 ARIA 标签

Example: ThemeToggle with i18n
---
import { useTranslations } from "@/i18n/ui";
const t = useTranslations(lang);
---

<button
 aria-label={t("nav.dark_mode")}
>
 {/*  Icon  */}
</button>

第 4 节:减少动效支持(2.3.3 AAA)

4.1 prefers-reduced-motion 媒体查询

为前庭障碍(眩晕、恶心)用户或不适应动画的用户,需要尊重 OS 级的 prefers-reduced-motion 设置

src/styles/ux.css
/* Reduced Motion (WCAG 2.3.3 AAA) */
@media (prefers-reduced-motion: reduce) {
 *,
 ::before,
 ::after {
 animation-duration: 0.01s !important;
 animation-iteration-count: 1 !important;
 transition-duration: 0.01s !important;
 scroll-behavior: auto !important;
 }

 .pulse,
 .skeleton {
 animation: none !important;
 }
}
ℹ️

注意 : 不仅要设置 animation: nonescroll-behavior: auto 也很重要。smooth 滚动对对运动敏感的用户同样可能不适。

4.2 应该禁用什么?

  • ✅ Transitions(淡入淡出、滑动、缩放)
  • ✅ Keyframe 动画(pulse、rotate、shake)
  • ✅ 平滑滚动
  • 不禁用 :Hover 效果(如悬停时颜色变化等静态变化是 OK 的)

第 5 节:PWA 与离线体验

5.1 @vite-pwa/astro 设置

Installation
pnpm add -D @vite-pwa/astro
astro.config.mjs
import AstroPWA from "@vite-pwa/astro";

export default defineConfig({
 integrations: [
 AstroPWA({
 registerType: "autoUpdate",
 includeAssets: ["favicon.svg", "apple-touch-icon.png"],
 manifest: {
 name: "HonoGear",
 short_name: "HonoGear",
 description: "Latest gadgets and tech news",
 theme_color: "#ffffff",
 background_color: "#ffffff",
 display: "standalone",
 icons: [
 {
 src: "android-chrome-192x192.png",
 sizes: "192x192",
 type: "image/png",
 },
 {
 src: "android-chrome-512x512.png",
 sizes: "512x512",
 type: "image/png",
 purpose: "any maskable",
 },
 ],
 },
 workbox: {
 // Next section...
 },
 }),
 ],
});

5.2 Workbox 运行时缓存策略

Workbox Configuration (astro.config.mjs)
workbox: {
 globPatterns: ["**/*.{js,css,html,ico,png,svg,webp,avif,woff,woff2}"],
 runtimeCaching: [{
 urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i,
 handler: "CacheFirst",
 options: {
 cacheName: "google-fonts",
 expiration: {
 maxEntries: 10,
 maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year
 },
 },
 }, {
 urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp|avif)$/i,
 handler: "StaleWhileRevalidate",
 options: {
 cacheName: "images",
 expiration: {
 maxEntries: 100,
 maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days
 },
 },
 }, {
 urlPattern: /^https:\/\/m\.media-amazon\.com\/.*/i,
 handler: "StaleWhileRevalidate",
 options: {
 cacheName: "amazon-images",
 expiration: {
 maxEntries: 50,
 maxAgeSeconds: 60 * 60 * 24 * 7, // 7 days
 },
 },
 }]
}

5.3 缓存策略说明

StrategyUse CaseBehavior
CacheFirstStatic assets (Fonts, CSS, JS)优先缓存;只有缓存未命中时才访问网络
StaleWhileRevalidateImages, “API data立即返回缓存,同时在后台更新
NetworkFirstReal-time data优先网络;离线时才回退到缓存
💡

推荐 : 博客文章图片几乎不变,因此 StaleWhileRevalidate 最合适。用户能立刻看到图片,下次访问时也会预热到最新版。

5.4 Service Worker 验证

Build 后的检查方式:

Build Verification
pnpm build

# Check generated files
ls dist/client/sw.js
ls dist/client/workbox-*.js

期望的输出示例:

PWA v1.2.0
mode generateSW
precache 274 entries (44680.64 KiB)
files generated
 dist/client/sw.js
 dist/client/workbox-9d4d28fe.js

第 6 节:实施路线图

1

1. 焦点管理

添加 `:focus-visible` CSS + Skip Link

15 分钟
2

2. 目标大小审计

更新按钮为 `min-w/h-[44px]`

30 分钟
3

3. ARIA 集成

添加 `aria-pressed`、`aria-expanded`、`aria-label`

45 分钟
4

4. 减少动效

实现 `prefers-reduced-motion` CSS

10 分钟
5

5. PWA 设置

安装 @vite-pwa/astro + Manifest

20 分钟
6

6. Workbox 缓存

配置运行时缓存策略

30 分钟
7

7. 测试

Lighthouse + 手动键盘导航

60 分钟

第 7 节:测试与验证

7.1 手动测试清单

键盘导航 :

  • Tab: 所有可交互元素是否可聚焦?
  • Shift + Tab: 是否能逆序导航?
  • Enter / Space: 按钮与链接是否正确触发?
  • Escape: 模态/对话框是否能关闭?

屏幕阅读器 (NVDA / VoiceOver):

  • Skip Link 是否最先被朗读?
  • aria-pressed 是否能读出 true/false?
  • aria-expanded 是否能传达菜单开合状态?

减少动效 :

  • 在 OS 设置中启用“减少动效”
  • 动画是否立即完成(或被禁用)?

7.2 自动化工具

Lighthouse Accessibility Audit
# Lighthouse CLI
npx lighthouse https://your-site.com --only-categories="accessibility" --view

# Chrome DevTools
# 1. Open DevTools (F12)
# 2. Lighthouse Tab
# 3. Select "Accessibility" + "Mobile/Desktop"
# 4. Generate Report

目标分数 : 95+ (100 理想,但实际 95 以上就很优秀)

关键指标 :

  • 所有元素都可见焦点
  • 无缺失的 ARIA 标签
  • 颜色对比度 4.5:1(AA)或 7:1(AAA)

7.3 浏览器扩展

  1. axe DevTools : 最准确的自动检测工具
  2. WAVE : 以可视化标注呈现,易于理解
  3. Accessibility Insights for Web : 微软出品,基于流程的测试

第 8 节:超越合规(高级体验)

8.1 为高阶用户设置键盘快捷键

Example: Cmd+K Search
document.addEventListener("keydown", (e) => {
 if ((e.metaKey || e.ctrlKey) && e.key === "k") {
 e.preventDefault();
 window.openSearch?.();
 }
});

8.2 使用 ARIA Live Region 的 Toast 通知

Accessible Toast
<div" role="status
 aria-live="polite"
 aria-atomic="true" class="toast" />
 {message}
</div>
  • aria-live="polite": 不打断当前朗读,在下一个停顿处提示
  • aria-live="assertive": 立即朗读(仅限错误等紧急情况)

8.3 颜色对比度审计

⚠️

常见错误 : 忘记检查暗色模式的对比度!即使浅色模式是 4.5:1,暗色模式也可能变成 3:1。

工具 :

  • WebAIM Contrast Checker
  • Chrome DevTools: Inspect Element → Color Picker → Contrast Ratio

结论:可访问性是竞争优势

2026 年,可访问性既是法律义务,也是业务层面的竞争优势

  • SEO 影响 : Google 更重视结构化、语义化的 HTML
  • 性能 : 轻量 HTML/CSS = 更快加载 = Core Web Vitals 提升
  • 触达 : 15% 的残障人群 + 处于临时限制(如单手被占用)场景的所有用户

不必一次性完成全部改造。 先从 ThemeToggle 开始 。只要加入 44x44px 的目标大小与 aria-label,就能立刻满足 WCAG 2.2 的多项标准。

引用: YouTube

Next Steps :

  1. pnpm add -D @vite-pwa/astro
  2. 复制第 5.2 节的 Workbox 配置
  3. 运行 pnpm build 并确认生成 sw.js
  4. 使用 Lighthouse Accessibility Audit 进行测试

Happy coding, and remember: 可访问性是为所有人而设计