重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
accessibility by exceptionless/exceptionless
npx skills add https://github.com/exceptionless/exceptionless --skill accessibility<!-- Use semantic elements -->
<header>
<nav aria-label="Main navigation">
<a href="/dashboard">Dashboard</a>
<a href="/projects">Projects</a>
</nav>
</header>
<main id="main-content">
<h1>Page Title</h1>
<section aria-labelledby="section-heading">
<h2 id="section-heading">Section Title</h2>
<article>...</article>
</section>
</main>
<footer>...</footer>
<!-- At top of layout -->
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute ...">
Skip to main content
</a>
<!-- Visible label -->
<label for="email">Email address</label>
<input id="email" type="email" />
<!-- Or using aria-label for icon-only inputs -->
<input type="search" aria-label="Search events" />
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
<label for="name">Name <span aria-hidden="true">*</span></label>
<input id="name" required aria-required="true" />
<input
id="email"
aria-invalid={hasError}
aria-describedby={hasError ? 'email-error' : undefined}
/>
{#if hasError}
<p id="email-error" class="text-destructive">
Please enter a valid email address
</p>
{/if}
aria-describedby 关联显示内联错误信息<!-- Natural tab order follows DOM order -->
<button>First</button>
<button>Second</button>
<button>Third</button>
<!-- Remove from tab order when hidden -->
<div hidden>
<button tabindex="-1">Hidden button</button>
</div>
// When dialog opens, focus first interactive element
$effect(() => {
if (open) {
dialogRef?.querySelector("input, button")?.focus();
}
});
// When dialog closes, return focus to trigger
const triggerRef = document.activeElement;
// ... on close
triggerRef?.focus();
<button
onkeydown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
}}
>
Action
</button>
<img src={user.avatar} alt={`Profile photo of ${user.name}`} />
<img src="/decorative-pattern.svg" alt="" aria-hidden="true" />
<button aria-label="Delete event">
<TrashIcon aria-hidden="true" />
</button>
<button>
<PlusIcon aria-hidden="true" />
Add Event
</button>
<!-- For dynamic updates (notifications, loading states) -->
<div aria-live="polite" aria-atomic="true">
{#if loading}
Loading events...
{/if}
</div>
<!-- For urgent messages -->
<div role="alert">
Error: Failed to save changes
</div>
<button
aria-expanded={isExpanded}
aria-controls="panel-content"
>
{isExpanded ? 'Collapse' : 'Expand'}
</button>
<div id="panel-content" hidden={!isExpanded}>
Panel content
</div>
<div role="tablist" aria-label="Event tabs">
<button role="tab" aria-selected={activeTab === 'details'}>
Details
</button>
<button role="tab" aria-selected={activeTab === 'stack'}>
Stack Trace
</button>
</div>
<div role="tabpanel" aria-labelledby="details-tab">
Tab content
</div>
最小对比度:普通文本为 4.5:1
大文本和 UI 组件为 3:1
不要仅依靠颜色来传达信息
<!-- ✅ Good: Icon + color + text --> <span class="text-destructive"> <AlertIcon aria-hidden="true" /> Error: Invalid input </span> <!-- ❌ Bad: Color only --><span class="text-destructive">Invalid input</span>
# Run axe-playwright audits in E2E tests
npm run test:e2e
// In Playwright tests
import AxeBuilder from "@axe-core/playwright";
test("page is accessible", async ({ page }) => {
await page.goto("/dashboard");
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
每周安装量
48
代码仓库
GitHub 星标数
2.5K
首次出现
2026年1月22日
安全审计
安装于
claude-code41
opencode40
gemini-cli37
codex36
cursor34
github-copilot32
<!-- Use semantic elements -->
<header>
<nav aria-label="Main navigation">
<a href="/dashboard">Dashboard</a>
<a href="/projects">Projects</a>
</nav>
</header>
<main id="main-content">
<h1>Page Title</h1>
<section aria-labelledby="section-heading">
<h2 id="section-heading">Section Title</h2>
<article>...</article>
</section>
</main>
<footer>...</footer>
<!-- At top of layout -->
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute ...">
Skip to main content
</a>
<!-- Visible label -->
<label for="email">Email address</label>
<input id="email" type="email" />
<!-- Or using aria-label for icon-only inputs -->
<input type="search" aria-label="Search events" />
<label for="name">Name <span aria-hidden="true">*</span></label>
<input id="name" required aria-required="true" />
<input
id="email"
aria-invalid={hasError}
aria-describedby={hasError ? 'email-error' : undefined}
/>
{#if hasError}
<p id="email-error" class="text-destructive">
Please enter a valid email address
</p>
{/if}
aria-describedby<!-- Natural tab order follows DOM order -->
<button>First</button>
<button>Second</button>
<button>Third</button>
<!-- Remove from tab order when hidden -->
<div hidden>
<button tabindex="-1">Hidden button</button>
</div>
// When dialog opens, focus first interactive element
$effect(() => {
if (open) {
dialogRef?.querySelector("input, button")?.focus();
}
});
// When dialog closes, return focus to trigger
const triggerRef = document.activeElement;
// ... on close
triggerRef?.focus();
<button
onkeydown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
}}
>
Action
</button>
<img src={user.avatar} alt={`Profile photo of ${user.name}`} />
<img src="/decorative-pattern.svg" alt="" aria-hidden="true" />
<button aria-label="Delete event">
<TrashIcon aria-hidden="true" />
</button>
<button>
<PlusIcon aria-hidden="true" />
Add Event
</button>
<!-- For dynamic updates (notifications, loading states) -->
<div aria-live="polite" aria-atomic="true">
{#if loading}
Loading events...
{/if}
</div>
<!-- For urgent messages -->
<div role="alert">
Error: Failed to save changes
</div>
<button
aria-expanded={isExpanded}
aria-controls="panel-content"
>
{isExpanded ? 'Collapse' : 'Expand'}
</button>
<div id="panel-content" hidden={!isExpanded}>
Panel content
</div>
<div role="tablist" aria-label="Event tabs">
<button role="tab" aria-selected={activeTab === 'details'}>
Details
</button>
<button role="tab" aria-selected={activeTab === 'stack'}>
Stack Trace
</button>
</div>
<div role="tabpanel" aria-labelledby="details-tab">
Tab content
</div>
Minimum contrast ratio: 4.5:1 for normal text
3:1 for large text and UI components
Don't rely on color alone to convey information
<!-- ✅ Good: Icon + color + text --> <span class="text-destructive"> <AlertIcon aria-hidden="true" /> Error: Invalid input </span> <!-- ❌ Bad: Color only --><span class="text-destructive">Invalid input</span>
# Run axe-playwright audits in E2E tests
npm run test:e2e
// In Playwright tests
import AxeBuilder from "@axe-core/playwright";
test("page is accessible", async ({ page }) => {
await page.goto("/dashboard");
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
Weekly Installs
48
Repository
GitHub Stars
2.5K
First Seen
Jan 22, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code41
opencode40
gemini-cli37
codex36
cursor34
github-copilot32
前端设计系统技能:生产级UI设计、设计令牌与可访问性指南
8,500 周安装