mcpField
T
tailwindcss-accessibility

tailwindcss-accessibility

github.com/josiahsiegel设计与 UI59297 次安装19 周前更新
代码仓库

关于 tailwindcss-accessibility

tailwindcss-accessibility 是使用 Tailwind CSS 实现无障碍模式的参考技能,以 WCAG 2.2 为依据,涵盖焦点、屏幕阅读器、对比度、动效、表单与测试。

主要功能

  1. 01焦点指示器与跳转链接
  2. 02屏幕阅读器内容模式
  3. 03对比度与减少动效指导
  4. 04无障碍表单与触控目标
  5. 05手动及自动化测试清单

适用场景

  1. 01为界面组件添加键盘焦点样式
  2. 02检查表单和控件的无障碍性
  3. 03依据 WCAG 模式检查 Tailwind 界面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/josiahsiegel/claude-plugin-marketplace --skill tailwindcss-accessibility

安装后在 Claude Code 中输入/tailwindcss-accessibility确认已加载。

详细介绍

Tailwind CSS 无障碍设计模式(WCAG 2.2 - 2025/2026)

WCAG 2.2 概览(当前标准)

WCAG 2.2 是当前的 W3C 标准。与 Tailwind 相关的主要新增内容:

  • 2.5.8 目标尺寸(AA 级):最小为 24x24 CSS 像素,建议为 44x44
  • 2.4.11 焦点不被遮挡:焦点指示器必须清晰可见
  • 2.4.13 焦点外观:对焦点指示器提出更严格的要求
  • 3.3.7 重复输入:不要要求重复输入信息
  • 3.2.6 一致的帮助:帮助机制应位于一致的位置

焦点管理

焦点环工具类

<button class="focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2">
  Button
</button>


<button class="focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500">
  Only shows ring for keyboard focus
</button>


<div class="focus-within:ring-2 focus-within:ring-brand-500 rounded-lg p-1">
  <input type="text" class="border-none focus:outline-none" />
</div>


@layer components {
  .focus-ring {
    @apply focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-2;
  }

  .focus-ring-inset {
    @apply focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-inset;
  }
}
跳过链接

<a
  href="#main-content"
  class="
    sr-only focus:not-sr-only
    focus:absolute focus:top-4 focus:left-4 focus:z-50
    focus:bg-white focus:px-4 focus:py-2 focus:rounded-md focus:shadow-lg
    focus:ring-2 focus:ring-brand-500
  "
>
  Skip to main content
</a>

<header>Navigation...</header>
<main id="main-content" tabindex="-1">
  Main content
</main>
焦点陷阱模式

<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  class="fixed inset-0 z-50 flex items-center justify-center"
>
  <div class="fixed inset-0 bg-black/50" aria-hidden="true"></div>
  <div
    class="relative bg-white rounded-xl p-6 max-w-md w-full"
    role="document"
  >
    <h2 id="modal-title" class="text-lg font-semibold">Modal Title</h2>
    <p>Modal content</p>
    <button class="focus-ring">Close</button>
  </div>
</div>

屏幕阅读器工具类

视觉隐藏内容

<span class="sr-only">Additional context for screen readers</span>


<a href="#main" class="sr-only focus:not-sr-only">Skip to main</a>


<button>
  <svg aria-hidden="true">...</svg>
  <span class="sr-only">Close menu</span>
</button>


<label>
  <span class="sr-only">Search</span>
  <input type="search" placeholder="Search..." />
</label>
宣告动态内容

<div
  role="status"
  aria-live="polite"
  aria-atomic="true"
  class="sr-only"
>
  
  3 items added to cart
</div>


<div
  role="alert"
  aria-live="assertive"
  class="bg-red-100 text-red-800 p-4 rounded-lg"
>
  Error: Please correct the form
</div>

色彩对比度

高对比度模式

<p class="text-gray-700 bg-white">4.5:1 contrast ratio</p>
<p class="text-gray-500 bg-white">May not meet WCAG AA (3:1 min for large text)</p>


<h1 class="text-4xl text-gray-600 bg-white">Large text - 3:1 ratio OK</h1>


<button class="
  bg-brand-500 text-white
  border-2 border-brand-500
  focus:ring-2 focus:ring-brand-500 focus:ring-offset-2
">
  Accessible Button
</button>
深色模式对比度

<p class="text-gray-900 dark:text-gray-100">
  High contrast text
</p>

<p class="text-gray-600 dark:text-gray-400">
  Secondary text with adequate contrast
</p>


<p class="text-gray-400 dark:text-gray-600">
  ⚠️ May have contrast issues in dark mode
</p>
焦点指示器对比度
@theme {
  /* High contrast focus ring */
  --color-focus: oklch(0.55 0.25 250);
  --color-focus-offset: oklch(1 0 0);
}
<button class="
  focus:outline-none
  focus-visible:ring-2
  focus-visible:ring-[var(--color-focus)]
  focus-visible:ring-offset-2
  focus-visible:ring-offset-[var(--color-focus-offset)]
">
  High contrast focus
</button>

动效与动画

减少动态效果

<div class="
  animate-bounce
  motion-reduce:animate-none
">
  Bouncing element (static for motion-sensitive users)
</div>


<div class="
  transition-opacity duration-300
  motion-reduce:transition-none
">
  Fades in (instant for motion-sensitive)
</div>


<div class="
  transition-all
  hover:scale-105 hover:shadow-lg
  motion-reduce:hover:scale-100 motion-reduce:hover:shadow-md
">
  Scales on hover (shadow only for motion-sensitive)
</div>
安全的动画模式
@layer components {
  /* Animations that respect reduced motion */
  .animate-fade-in {
    @apply animate-in fade-in duration-300;
    @apply motion-reduce:animate-none motion-reduce:opacity-100;
  }

  .animate-slide-up {
    @apply animate-in slide-in-from-bottom-4 duration-300;
    @apply motion-reduce:animate-none motion-reduce:translate-y-0;
  }
}
悬停时暂停动画

<div class="
  animate-spin
  hover:animate-pause
  motion-reduce:animate-none
">
  Loading spinner
</div>

表单无障碍设计

无障碍表单字段
<div class="space-y-4">
  
  <div>
    <label for="email" class="block text-sm font-medium text-gray-700">
      Email address
      <span class="text-red-500" aria-hidden="true">*</span>
    </label>
    <input
      type="email"
      id="email"
      name="email"
      required
      aria-required="true"
      aria-describedby="email-hint email-error"
      class="
        mt-1 block w-full rounded-md border-gray-300
        focus:border-brand-500 focus:ring-brand-500
        aria-invalid:border-red-500 aria-invalid:ring-red-500
      "
    />
    <p id="email-hint" class="mt-1 text-sm text-gray-500">
      We'll never share your email
    </p>
    <p id="email-error" class="mt-1 text-sm text-red-600 hidden" role="alert">
      Please enter a valid email
    </p>
  </div>

  
  <div class="flex items-start gap-3">
    <input
      type="checkbox"
      id="terms"
      name="terms"
      class="
        h-4 w-4 rounded border-gray-300 text-brand-500
        focus:ring-brand-500
      "
    />
    <label for="terms" class="text-sm text-gray-700">
      I agree to the
      <a href="/terms" class="text-brand-500 underline">terms and conditions</a>
    </label>
  </div>

  
  <fieldset>
    <legend class="text-sm font-medium text-gray-700">Notification preference</legend>
    <div class="mt-2 space-y-2">
      <div class="flex items-center gap-3">
        <input type="radio" id="email-pref" name="notification" value="email" class="h-4 w-4" />
        <label for="email-pref">Email</label>
      </div>
      <div class="flex items-center gap-3">
        <input type="radio" id="sms-pref" name="notification" value="sms" class="h-4 w-4" />
        <label for="sms-pref">SMS</label>
      </div>
    </div>
  </fieldset>
</div>
错误状态

<div>
  <label for="password" class="block text-sm font-medium text-gray-700">
    Password
  </label>
  <input
    type="password"
    id="password"
    aria-invalid="true"
    aria-describedby="password-error"
    class="
      mt-1 block w-full rounded-md
      border-red-500 text-red-900
      focus:border-red-500 focus:ring-red-500
    "
  />
  <p id="password-error" class="mt-1 text-sm text-red-600" role="alert">
    <span class="sr-only">Error:</span>
    Password must be at least 8 characters
  </p>
</div>
表单验证反馈
/* Style based on aria-invalid attribute */
@custom-variant aria-invalid (&[aria-invalid="true"]);
<input
  class="
    border-gray-300
    aria-invalid:border-red-500
    aria-invalid:text-red-900
    aria-invalid:focus:ring-red-500
  "
  aria-invalid="true"
/>

交互组件

无障碍按钮

<button
  type="submit"
  aria-busy="true"
  aria-disabled="true"
  class="
    relative
    aria-busy:cursor-wait
    aria-disabled:opacity-50 aria-disabled:cursor-not-allowed
  "
>
  <span class="aria-busy:invisible">Submit</span>
  <span class="absolute inset-0 flex items-center justify-center aria-busy:visible invisible">
    <svg class="animate-spin h-5 w-5" aria-hidden="true">...</svg>
    <span class="sr-only">Loading...</span>
  </span>
</button>


<button
  type="button"
  aria-label="Close dialog"
  class="rounded-full p-2 hover:bg-gray-100 focus-ring"
>
  <svg aria-hidden="true" class="h-5 w-5">...</svg>
</button>


<button
  type="button"
  aria-pressed="false"
  class="
    px-4 py-2 rounded-lg border
    aria-pressed:bg-brand-500 aria-pressed:text-white aria-pressed:border-brand-500
  "
>
  <span class="sr-only">Toggle feature</span>
  Feature
</button>
无障碍下拉菜单
<div class="relative">
  <button
    type="button"
    aria-haspopup="menu"
    aria-expanded="false"
    aria-controls="dropdown-menu"
    class="flex items-center gap-2 px-4 py-2 rounded-lg border focus-ring"
  >
    Options
    <svg aria-hidden="true" class="h-4 w-4">...</svg>
  </button>

  <ul
    id="dropdown-menu"
    role="menu"
    aria-labelledby="dropdown-button"
    class="
      absolute top-full mt-1 w-48 rounded-lg bg-white shadow-lg border
      hidden aria-expanded:block
    "
  >
    <li role="none">
      <a
        href="#"
        role="menuitem"
        tabindex="-1"
        class="block px-4 py-2 hover:bg-gray-100 focus:bg-gray-100 focus:outline-none"
      >
        Edit
      </a>
    </li>
    <li role="none">
      <a
        href="#"
        role="menuitem"
        tabindex="-1"
        class="block px-4 py-2 hover:bg-gray-100 focus:bg-gray-100 focus:outline-none"
      >
        Delete
      </a>
    </li>
  </ul>
</div>
无障碍标签页
<div>
  <div role="tablist" aria-label="Account settings" class="flex border-b">
    <button
      role="tab"
      aria-selected="true"
      aria-controls="panel-1"
      id="tab-1"
      class="
        px-4 py-2 border-b-2
        aria-selected:border-brand-500 aria-selected:text-brand-500
        hover:text-gray-700
        focus-visible:ring-2 focus-visible:ring-inset
      "
    >
      Profile
    </button>
    <button
      role="tab"
      aria-selected="false"
      aria-controls="panel-2"
      id="tab-2"
      tabindex="-1"
      class="px-4 py-2 border-b-2 border-transparent"
    >
      Settings
    </button>
  </div>

  <div
    role="tabpanel"
    id="panel-1"
    aria-labelledby="tab-1"
    tabindex="0"
    class="p-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset"
  >
    Profile content
  </div>

  <div
    role="tabpanel"
    id="panel-2"
    aria-labelledby="tab-2"
    tabindex="0"
    hidden
    class="p-4"
  >
    Settings content
  </div>
</div>

触控目标(WCAG 2.2 - 2025/2026 年的关键要求)

WCAG 2.2 目标尺寸要求
级别要求Tailwind 类
AA(2.5.8)最小为 24x24 CSS 像素min-h-6 min-w-6
建议值44x44 CSS 像素min-h-11 min-w-11
AAA(2.5.5)44x44 CSS 像素min-h-11 min-w-11
最佳值48x48 CSS 像素min-h-12 min-w-12

平台指南比较:

  • Apple iOS:最小为 44x44 点
  • Google Android:最小为 48x48 dp
  • Microsoft Fluent:最小为 44x44 像素
最小触控目标尺寸

<button class="min-h-6 min-w-6 p-1">
  <svg class="h-4 w-4">...</svg>
</button>


<button class="min-h-11 min-w-11 p-2.5">
  <svg class="h-6 w-6" aria-hidden="true">...</svg>
  <span class="sr-only">Action</span>
</button>


<button class="min-h-12 min-w-12 px-6 py-3 text-base font-medium">
  Primary Action
</button>
将触控目标扩展至可见元素之外

<a href="#" class="relative inline-block text-sm">
  Small Link Text
  <span class="absolute -inset-3" aria-hidden="true"></span>
</a>


<button class="relative p-2 -m-2 rounded-lg hover:bg-gray-100">
  <svg class="h-5 w-5" aria-hidden="true">...</svg>
  <span class="sr-only">Close menu</span>
</button>


<article class="relative p-4 rounded-lg border hover:shadow-md">
  <h3>Card Title</h3>
  <p>Description text</p>
  <a href="/details" class="after:absolute after:inset-0">
    <span class="sr-only">View details</span>
  </a>
</article>
交互元素之间的间距

WCAG 2.2 要求间距为 24px,或目标尺寸至少为 24px:


<div class="flex gap-3">
  <button class="min-h-11 px-4 py-2">Button 1</button>
  <button class="min-h-11 px-4 py-2">Button 2</button>
</div>


<nav class="flex flex-col">
  <a href="#" class="py-3 px-4 min-h-11 border-b border-gray-100">Link 1</a>
  <a href="#" class="py-3 px-4 min-h-11 border-b border-gray-100">Link 2</a>
  <a href="#" class="py-3 px-4 min-h-11">Link 3</a>
</nav>


<div class="flex flex-wrap gap-3">
  <button class="min-h-11 px-4 py-2 border rounded-lg">Cancel</button>
  <button class="min-h-11 px-4 py-2 bg-blue-600 text-white rounded-lg">Confirm</button>
</div>
触控目标例外情况(WCAG 2.2)

以下目标可以小于 24x24:

  • 句子中的行内文本链接
  • 浏览器提供的控件(滚动条)
  • 尺寸对信息展示至关重要
  • 同一页面上存在更大的等效目标

文本无障碍设计

易读文本

<p class="leading-relaxed">
  Long form content with comfortable line height
</p>


<article class="max-w-prose">
  <p class="leading-relaxed">
    Content with optimal line length (45-75 characters)
  </p>
</article>


<div class="space-y-6">
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</div>
文本缩放

<p class="text-base">Scales with user's font size preferences</p>


<p class="text-[14px]">⚠️ Won't scale with browser zoom</p>


<div class="min-h-[auto]">
  Content height adjusts with text size
</div>

使用 Tailwind 编写语义化 HTML

地标区域
<body class="min-h-screen flex flex-col">
  <header class="sticky top-0 bg-white shadow z-50">
    <nav aria-label="Main navigation">...</nav>
  </header>

  <main id="main-content" class="flex-1">
    <article>
      <h1>Page Title</h1>
      <section aria-labelledby="section-1">
        <h2 id="section-1">Section Title</h2>
        <p>Content...</p>
      </section>
    </article>

    <aside aria-label="Related content" class="hidden lg:block">
      Sidebar content
    </aside>
  </main>

  <footer class="bg-gray-800 text-white">
    <nav aria-label="Footer navigation">...</nav>
  </footer>
</body>
标题层级
<article class="prose">
  <h1 class="text-4xl font-bold">Main Title (H1)</h1>

  <section>
    <h2 class="text-2xl font-semibold">Section (H2)</h2>

    <section>
      <h3 class="text-xl font-medium">Subsection (H3)</h3>
      <p>Content...</p>
    </section>
  </section>
</article>

无障碍测试

浏览器开发者工具检查清单
  1. 色彩对比度:使用对比度检查工具
  2. 焦点顺序:按 Tab 键遍历页面
  3. 缩放:测试 200% 缩放
  4. 减少动态效果:在操作系统设置中启用
自动化测试
// axe-core integration
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

test('component is accessible', async () => {
  const { container } = render(<Component />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

最佳实践总结

模式实现方式WCAG 级别
焦点可见focus-visible:ring-2 focus-visible:ring-offset-22.4.7(AA)
仅供屏幕阅读器使用sr-only1.3.1(A)
跳过链接sr-only focus:not-sr-only focus:absolute2.4.1(A)
减少动态效果motion-reduce:animate-none motion-reduce:transition-none2.3.3(AAA)
触控目标(最小值)min-h-6 min-w-6(24px)2.5.8(AA)
触控目标(建议值)min-h-11 min-w-11(44px)2.5.5(AAA)
触控间距gap-3(目标之间至少间隔 12px)2.5.8(AA)
文本对比度普通文本为 4.5:1,大号文本为 3:11.4.3(AA)
表单错误aria-invalid="true" + role="alert"3.3.1(A)
焦点不被遮挡避免使用 z-index 覆盖聚焦元素2.4.11(AA)
适合触控的按钮
<button
  type="button"
  class="
    min-h-11 min-w-11 px-4 py-2.5
    text-sm md:text-base font-medium
    bg-blue-600 text-white hover:bg-blue-700
    focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2
    rounded-lg disabled:opacity-50 disabled:cursor-not-allowed
    transition-colors motion-reduce:transition-none
  "
>
  Button Text
</button>

评论

支持 Markdown