From ui-design
WCAG 2.2 accessibility guidelines with actionable implementation rules for web developers. Covers contrast ratios, keyboard navigation, focus management, ARIA, screen readers, alt text, touch targets, form validation, color blindness, reduced motion, semantic HTML, and landmarks. Apply whenever writing HTML/CSS/JS, building forms, handling focus, choosing colors for accessibility, implementing interactive components, or reviewing code for a11y compliance. Includes all Level A + AA criteria and new 2.2 additions (focus not obscured, dragging alternatives, target size minimum, redundant entry, accessible authentication, consistent help).
How this skill is triggered — by the user, by Claude, or both
Slash command
/ui-design:wcag-2.2The summary Claude sees in its skill listing — used to decide when to auto-load this skill
| Element | Minimum ratio | Rule |
| Element | Minimum ratio | Rule |
|---|---|---|
| Normal text (<18px / <14px bold) | 4.5:1 | 1.4.3 AA |
| Large text (>=18px / >=14px bold) | 3:1 | 1.4.3 AA |
| UI components & graphical objects | 3:1 | 1.4.11 AA |
| Enhanced (AAA) normal text | 7:1 | 1.4.6 AAA |
| Enhanced (AAA) large text | 4.5:1 | 1.4.6 AAA |
| Focus indicator vs adjacent | 3:1 | 2.4.13 AAA |
| Level | Minimum | Rule |
|---|---|---|
| AA | 24x24 CSS px (or adequate spacing) | 2.5.8 NEW |
| AAA | 44x44 CSS px | 2.5.5 |
<img> needs alt text. Decorative images: alt=""aria-describedbyaria-label on the button, aria-hidden="true" on the icon<svg>: use role="img" + aria-label or <title> inside<!-- Informative -->
<img src="chart.png" alt="Sales increased 40% in Q3 2024">
<!-- Decorative -->
<img src="divider.svg" alt="" role="presentation">
<!-- Icon button -->
<button aria-label="Close dialog">
<svg aria-hidden="true">...</svg>
</button>
<nav>, <main>, <aside>, <header>, <footer>, <section><h1>, don't skip levels (h1 -> h3)<ul>/<ol>/<dl>, not <div> with bullets<th scope="col/row">, <caption>, never for layout<label for="id">, <fieldset>/<legend> for groups<form>
<fieldset>
<legend>Shipping Address</legend>
<label for="street">Street</label>
<input id="street" type="text" autocomplete="street-address">
</fieldset>
</form>
order, flex-direction: row-reverse) to reorder visually if it breaks reading sequence@media (orientation: portrait) is fine for styling, but don't hide contentautocomplete attributes on form fields that collect personal data<input type="text" autocomplete="given-name">
<input type="email" autocomplete="email">
<input type="tel" autocomplete="tel">
<input type="text" autocomplete="street-address">
<input type="text" autocomplete="postal-code">
<input type="text" autocomplete="cc-number">
/* Bad: only color */
.error { color: red; }
/* Good: color + icon + text */
.error {
color: #CF1124;
border-left: 4px solid #CF1124;
}
.error::before { content: "⚠ "; }
/* Meets 4.5:1 on white */
color: #595959; /* 7:1 — safe */
color: #767676; /* 4.54:1 — minimum for normal text */
/* Meets 3:1 on white for UI components */
border-color: #949494; /* 3.03:1 */
rem/em for font sizes, not pxmax-height with overflow: hidden on text containers/* Good: responsive container */
.container { max-width: 100%; overflow-wrap: break-word; }
/* Bad: fixed width forcing scroll */
.container { width: 1200px; }
* {
line-height: 1.5em !important;
letter-spacing: 0.12em !important;
word-spacing: 0.16em !important;
}
p { margin-bottom: 2em !important; }
/* Good: hoverable tooltip */
.tooltip-trigger:hover + .tooltip,
.tooltip-trigger:focus + .tooltip,
.tooltip:hover {
display: block;
}
<button>, <a href>, <input>, <select>tabindex="0" + keyboard event handlerstabindex="-1": focusable via JS only (e.g., modal containers)tabindex="0": in natural tab ordertabindex > 0<!-- Bad: div with click handler only -->
<div onclick="doThing()">Click me</div>
<!-- Good: button -->
<button onclick="doThing()">Click me</button>
<!-- Good: custom widget with keyboard support -->
<div role="button" tabindex="0"
onclick="doThing()"
onkeydown="if(event.key==='Enter'||event.key===' ')doThing()">
Click me
</div>
<body>
<a href="#main" class="skip-link">Skip to main content</a>
<nav>...</nav>
<main id="main">...</main>
</body>
.skip-link {
position: absolute;
top: -40px;
left: 0;
padding: 8px 16px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
<title>aria-label or aria-describedby when visual context is needed<!-- Bad -->
<a href="/plans">Read more</a>
<!-- Good -->
<a href="/plans">View pricing plans</a>
<!-- Good: when visual design needs "Read more" -->
<a href="/plans" aria-label="Read more about pricing plans">Read more</a>
outline: none without a replacement/* Good: custom focus style */
:focus-visible {
outline: 2px solid #0967D2;
outline-offset: 2px;
}
/* Remove default only when providing custom */
:focus:not(:focus-visible) {
outline: none;
}
scroll-padding to account for fixed elementshtml {
scroll-padding-top: 80px; /* height of sticky header */
scroll-padding-bottom: 60px; /* height of sticky footer */
}
aria-label can be "Search products" but not "Find items"<!-- Drag-to-reorder with button alternatives -->
<li draggable="true">
<span>Item 1</span>
<button aria-label="Move Item 1 up">↑</button>
<button aria-label="Move Item 1 down">↓</button>
</li>
/* Meets 2.5.8 */
button, a, input, select, [role="button"] {
min-width: 24px;
min-height: 24px;
}
/* Better: aim for 44x44 (2.5.5 AAA) */
.touch-target {
min-width: 44px;
min-height: 44px;
padding: 12px;
}
lang attribute on <html>: <html lang="en"><span lang="de">Danke</span><label for="email">Email</label>
<input id="email" type="email" aria-describedby="email-error" aria-invalid="true">
<p id="email-error" role="alert" class="error">
Please enter a valid email address (e.g., [email protected])
</p>
*<label for="phone">
Phone number <span class="required">(required)</span>
</label>
<input id="phone" type="tel" aria-describedby="phone-hint" required>
<p id="phone-hint" class="hint">Format: +1 (555) 123-4567</p>
<!-- Custom toggle: needs role + state -->
<button role="switch" aria-checked="false" onclick="toggle(this)">
Dark mode
</button>
<!-- Custom tab panel -->
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel-1">Tab 1</button>
<button role="tab" aria-selected="false" aria-controls="panel-2">Tab 2</button>
</div>
<div role="tabpanel" id="panel-1">Content 1</div>
<div role="tabpanel" id="panel-2" hidden>Content 2</div>
role="status" (polite) or role="alert" (assertive)aria-live="polite" for non-urgent updates<!-- Toast notification -->
<div role="status" aria-live="polite">
Your changes have been saved.
</div>
<!-- Error alert -->
<div role="alert">
Payment failed. Please check your card details.
</div>
<!-- Live region for dynamic content -->
<div aria-live="polite" aria-atomic="true">
Showing 1-10 of 42 results
</div>
<header role="banner"> <!-- site header, once per page -->
<nav role="navigation"> <!-- navigation, label with aria-label if multiple -->
<main role="main"> <!-- main content, once per page -->
<aside role="complementary"> <!-- sidebar, related content -->
<footer role="contentinfo"> <!-- site footer, once per page -->
<form role="search"> <!-- search form -->
<!-- Modal dialog -->
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm deletion</h2>
<p>Are you sure?</p>
<button>Cancel</button>
<button>Delete</button>
</div>
<!-- Accordion -->
<h3>
<button aria-expanded="false" aria-controls="section-1">Section Title</button>
</h3>
<div id="section-1" role="region" hidden>Content</div>
<!-- Loading state -->
<div aria-busy="true" aria-live="polite">Loading...</div>
<!-- Progress -->
<div role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">
75%
</div>
| State | Use for |
|---|---|
aria-expanded | Accordions, dropdowns, menus |
aria-selected | Tabs, listbox options |
aria-checked | Checkboxes, switches, toggles |
aria-pressed | Toggle buttons |
aria-current="page" | Active nav item |
aria-invalid="true" | Form fields with errors |
aria-disabled="true" | Non-interactive disabled state |
aria-hidden="true" | Decorative elements, hidden from AT |
aria-busy="true" | Region currently updating |
@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;
}
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1F2933;
--text: #F5F7FA;
}
}
role="alert")aria-live)prefers-reduced-motionnpx claudepluginhub joshuarweaver/cascade-content-creation-misc-1 --plugin svengraziani-ui-designGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Dispatches multiple subagents concurrently for independent tasks without shared state. Use when facing 2+ unrelated failures or subsystems that can be investigated in parallel.