Loading...
Loading...
Ensure accessibility in UI components including semantic HTML, ARIA attributes, keyboard navigation, and WCAG 2.1 AA compliance.
npx skill4agent add oimiragieo/agent-studio accessibility<header><nav><main><article><section><aside><footer><div><button><div onclick><a><form><input><label><h1><h6><!-- ❌ BAD -->
<div class="header">
<div class="nav">
<div class="nav-item" onclick="navigate()">Home</div>
</div>
</div>
<!-- ✅ GOOD -->
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>| Use Case | ARIA Attributes | Example |
|---|---|---|
| Custom button | | |
| Modal dialog | | |
| Alert | | |
| Tab panel | | |
<button role="button">aria-labelaria-hidden="true"aria-live<!-- Icon button needs aria-label -->
<button aria-label="Close dialog">
<i class="icon-close" aria-hidden="true"></i>
</button>
<!-- Dynamic content needs live region -->
<div role="alert" aria-live="assertive">Form submitted successfully</div>:focus// Focus trap in modal
function openModal(modal) {
modal.style.display = 'block';
const firstFocusable = modal.querySelector('button, input, a');
firstFocusable.focus();
trapFocus(modal); // Prevent escape from modal
}
function trapFocus(container) {
const focusableElements = container.querySelectorAll('button, input, select, textarea, a[href]');
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
container.addEventListener('keydown', e => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstElement) {
lastElement.focus();
e.preventDefault();
} else if (!e.shiftKey && document.activeElement === lastElement) {
firstElement.focus();
e.preventDefault();
}
}
});
}| Text Size | WCAG AA | WCAG AAA |
|---|---|---|
| Normal text (< 18pt) | 4.5:1 | 7:1 |
| Large text (≥ 18pt or 14pt bold) | 3:1 | 4.5:1 |
| UI components | 3:1 | - |
/* ❌ BAD - Insufficient contrast */
.text {
color: #777;
background: #fff;
} /* 4.47:1 - fails AA */
/* ✅ GOOD - Sufficient contrast */
.text {
color: #595959;
background: #fff;
} /* 7:1 - passes AAA */
/* ✅ GOOD - Don't rely on color alone */
.error {
color: #d00;
border-left: 4px solid #d00; /* Visual indicator beyond color */
}
.error::before {
content: '⚠️ ';
} /* Icon indicator */<!-- ❌ BAD - Missing or redundant alt -->
<img src="logo.png" />
<img src="decorative.png" alt="decorative image" />
<!-- ✅ GOOD -->
<img src="logo.png" alt="Company Logo" />
<img src="decorative.png" alt="" role="presentation" /><!-- ❌ BAD - No label for screen readers -->
<button><i class="icon-delete"></i></button>
<!-- ✅ GOOD -->
<button aria-label="Delete item">
<i class="icon-delete" aria-hidden="true"></i>
</button><!-- Announce errors immediately -->
<div role="alert" aria-live="assertive">Error: Invalid email address</div>
<!-- Announce status updates politely -->
<div aria-live="polite" aria-atomic="true">Loading results... 3 of 10 loaded</div><label><fieldset><legend>aria-describedbyaria-required="true"required<!-- ✅ GOOD Form Structure -->
<form>
<fieldset>
<legend>Personal Information</legend>
<label for="name">Name (required)</label>
<input id="name" type="text" required aria-required="true" aria-describedby="name-error" />
<span id="name-error" role="alert" class="error" aria-live="polite">
<!-- Error message appears here -->
</span>
<label for="email">Email</label>
<input id="email" type="email" aria-describedby="email-hint" />
<span id="email-hint" class="hint">We'll never share your email</span>
</fieldset>
</form>Skill({ skill: 'accessibility' });Skill({ skill: 'accessibility', args: 'color-contrast' });Skill({ skill: 'accessibility', args: 'forms' });<div>tabindex| Anti-Pattern | Problem | Fix |
|---|---|---|
| Not keyboard accessible | Use |
| No alt text | Screen readers can't describe | Add meaningful |
| Color-only info | Color blind users miss it | Add text/icons |
| No focus indicators | Users lost in navigation | Add |
| Auto-play media | Disruptive for screen readers | Add controls, pause option |
| No semantic structure | Use semantic HTML |
alt="".claude/rules/accessibility.mdcat .claude/context/memory/learnings.md.claude/context/memory/learnings.md.claude/context/memory/issues.md.claude/context/memory/decisions.mdASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.