원클릭으로
accessibility
WCAG 2.1 AA patterns for inclusive UI. Load when building components needing a11y, ARIA, or keyboard nav
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
WCAG 2.1 AA patterns for inclusive UI. Load when building components needing a11y, ARIA, or keyboard nav
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
External API evaluation and MCP server wrapper generation. Load when integrating a REST/GraphQL API as a workspace tool
Capability-based provider system patterns. Load when creating providers or wiring module-provider integration
Full-stack WebSocket subscription development. Load when adding WS routes, topic services, or debugging WS data flows
Systematic Python type error resolution (mypy/pyright). Load when fixing backend type check failures
Type-first Python with Pydantic, Protocol, and discriminated unions. Load when writing models or enforcing typing
Systematic type error resolution for Python (mypy/pyright) and TypeScript (vue-tsc). Use when fixing type errors, resolving type check failures, or optimizing type imports.
| name | accessibility |
| description | WCAG 2.1 AA patterns for inclusive UI. Load when building components needing a11y, ARIA, or keyboard nav |
| user-invocable | false |
Actionable WCAG 2.1 AA patterns for building inclusive UI — semantic HTML, keyboard navigation, ARIA, focus management, color contrast, and testing checklists.
| Principle | Description |
|---|---|
| Perceivable | Content can be perceived through different senses |
| Operable | Interface can be operated by all users |
| Understandable | Content and interface are understandable |
| Robust | Content works with assistive technologies |
Use native HTML elements before reaching for ARIA:
<!-- ❌ ARIA role on div -->
<div role="button" tabindex="0">Click me</div>
<!-- ✅ Native button -->
<button>Click me</button>
Landmark regions:
<header><!-- site header --></header>
<nav aria-label="Main"><!-- primary nav --></nav>
<main id="main-content"><!-- page content --></main>
<aside><!-- sidebar --></aside>
<footer><!-- site footer --></footer>
Skip link (first focusable element):
<a href="#main-content" class="skip-link">Skip to main content</a>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px 16px;
z-index: 100;
}
.skip-link:focus { top: 0; }
All interactive elements must be keyboard-operable:
| Key | Action |
|---|---|
Tab | Move to next interactive element |
Shift+Tab | Move to previous interactive element |
Enter / Space | Activate focused control |
Arrow | Navigate within composite widgets (tabs, menus, grids) |
Escape | Close dialogs, menus, popovers |
Rules:
tabindex (except tabindex="-1" for programmatic focus targets like headings)Focus visibility:
/* Never remove focus outlines globally */
:focus-visible {
outline: 2px solid var(--color-focus, #005fcc);
outline-offset: 2px;
}
Roving tabindex for composite widgets (tabs, grids):
tabindex="0"tabindex="-1"Text alternatives:
<!-- Images: descriptive alt -->
<img src="chart.png" alt="Bar chart showing 40% increase in Q3 sales">
<!-- Decorative images: empty alt -->
<img src="border.png" alt="" role="presentation">
<!-- Icon buttons: accessible name -->
<button aria-label="Close dialog">
<svg aria-hidden="true"><!-- icon --></svg>
</button>
ARIA states for interactive widgets:
<!-- Expandable sections -->
<button aria-expanded="false" aria-controls="panel-1">Details</button>
<div id="panel-1" hidden>Content</div>
<!-- Invalid form fields -->
<input aria-invalid="true" aria-describedby="email-error">
<p id="email-error" role="alert">Please enter a valid email</p>
<!-- Loading states -->
<button aria-busy="true" aria-live="polite" disabled>Loading...</button>
Custom tabs pattern:
<div role="tablist" aria-label="Product info">
<button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">Desc</button>
<button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" tabindex="-1">Reviews</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">...</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>...</div>
| Element | AA Minimum Ratio |
|---|---|
| Normal text (< 18px / < 14px bold) | 4.5:1 |
| Large text (>= 18px / >= 14px bold) | 3:1 |
| UI components & graphics | 3:1 |
Never rely on color alone to convey meaning:
<!-- ❌ Only color indicates error -->
<input style="border-color: red">
<!-- ✅ Color + icon + text -->
<input aria-invalid="true" aria-describedby="err">
<span id="err">
<svg aria-hidden="true"><!-- error icon --></svg>
Please enter a valid email
</span>
<!-- Explicit label association -->
<label for="email">Email address</label>
<input type="email" id="email" autocomplete="email" required
aria-describedby="email-hint">
<p id="email-hint">We'll never share your email</p>
On validation failure:
aria-invalid="true" on the fieldaria-describedbyrole="alert" or aria-live="assertive" on the error container<!-- Polite: announced after current speech -->
<div aria-live="polite" aria-atomic="true">
3 items in your cart
</div>
<!-- Assertive: interrupts current speech (use sparingly) -->
<div role="alert" aria-live="assertive">
Connection lost. Reconnecting...
</div>
/* Respect reduced motion preference */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Modal dialog focus trap:
Route transitions (SPA):
alt textlang attribute on <html>main, nav, header, footer)aria-live for dynamic content updatesprefers-reduced-motion respected<div> or <span> for interactive elements instead of <button>, <a>, <input>*:focus { outline: none } without replacement focus stylesplaceholder as the only label (disappears on input, poor contrast)<nav role="navigation"> is unnecessary)