.claude/skills/accessibility/SKILL.md
Ensure accessibility in UI components including semantic HTML, ARIA attributes, keyboard navigation, and WCAG 2.2 AA compliance.
npx skillsauth add oimiragieo/agent-studio accessibilityInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Review component structure for proper semantic elements:
Check for:
<header>, <nav>, <main>, <article>, <section>, <aside>, <footer> instead of generic <div><button> for clickable elements (not <div onclick>)<a> for navigation links<form>, <input>, <label> for forms<h1> through <h6>)Example:
<!-- ❌ BAD -->
<div class="header">
<div claass="nav">
<div class="nav-item" onclick="navigate()">Home</div>
</div>
</div>
<!-- ✅ GOOD -->
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
Add ARIA attributess ONLY when semantic HTML is insufficient:
Common Patterns:
| Use Case | ARIA Attributes | Example |
| ------------- | --------------------------------------- | ----------------------------------------------- |
| Custom button | role="button", tabindex="0" | <div role="button" tabindex="0"> |
| Modal dialog | role="dialog", aria-modal="true" | <div role="dialog" aria-modal="true"> |
| Alert | role="alert", aria-live="assertive" | <div role="alert">Error occurred</div> |
| Tab panel | role="tabpanel", aria-labelledby | <div role="tabpanel" aria-labelledby="tab-1"> |
Rules:
<button role="button"> is unnecessary)aria-label for icon buttons without textaria-hidden="true" for decorative elementsaria-live regions for dynamic contentExample:
<!-- 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>
Verify all interactive elements are keyboard accessible:
Requirements:
Focus Management:
:focus styles)Example:
// 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();
}
}
});
}
WCAG 2.2 added 9 new success criteria. Verify compliance with the applicable ones:
When a keyboard-focused element scrolls into view, it must not be completely hidden by sticky headers/footers or overlapping UI. At least part of the focused element must always be visible.
/* Prevent focus from hiding behind sticky headers */
:focus-visible {
scroll-margin-top: 80px; /* Height of sticky header + buffer */
scroll-margin-bottom: 60px; /* Height of sticky footer + buffer */
}
Test: Tab through all interactive elements — verify none are fully hidden behind banners, cookie notices, or sticky bars.
The focused component must be fully visible (not just partially). Sticky UI must not overlap focus at all.
Focus indicators must have: area of at least the perimeter of the unfocused component times 2 CSS pixels, and contrast ratio of at least 3:1 between focused and unfocused states.
/* Meeting 2.4.13 Focus Appearance (AAA) */
:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
/* 3px thickness > 2px minimum; #005fcc on white = 7.1:1 contrast */
}
All drag-and-drop functionality MUST have a single-pointer (click/tap) alternative. Users who cannot perform precise drag gestures must be able to accomplish the same task.
<!-- Sortable list: drag alternative via buttons -->
<ul>
<li draggable="true" id="item-1">
Item 1
<button aria-label="Move Item 1 up" onclick="moveUp('item-1')">↑</button>
<button aria-label="Move Item 1 down" onclick="moveDown('item-1')">↓</button>
</li>
</ul>
<!-- Slider: keyboard alternative provided natively -->
<input type="range" min="0" max="100" value="50" aria-label="Volume" />
<!-- Alternatively: add an input[type=number] companion -->
<input type="number" min="0" max="100" value="50" aria-label="Volume value" />
Test: Identify all drag interactions. Verify each has a non-drag equivalent (buttons, context menus, keyboard shortcuts).
All pointer targets (buttons, links, checkboxes, radio inputs) must be at least 24x24 CSS pixels, OR have sufficient spacing so the 24x24 area does not overlap another target.
/* Ensure minimum target size */
button,
a,
input[type='checkbox'],
input[type='radio'],
select {
min-width: 24px;
min-height: 24px;
}
/* Inline links: use padding to increase hit area without visual size change */
a {
padding: 4px 0;
}
/* Recommended: 44x44 CSS pixels for primary actions (mobile-friendly) */
.primary-action {
min-width: 44px;
min-height: 44px;
}
Test: Measure all interactive elements. Verify none fall below 24x24 CSS pixels (use browser DevTools element inspector).
If a help mechanism (contact link, chat widget, phone number, FAQ link) appears on multiple pages, it must appear in the same relative order in the page content.
<!-- Help mechanism must appear consistently across pages -->
<footer>
<nav aria-label="Help resources">
<!-- This order must not change between pages -->
<a href="/faq">FAQ</a>
<a href="/contact">Contact Support</a>
<a href="tel:+18005551234">1-800-555-1234</a>
</nav>
</footer>
Test: Navigate between pages. Verify help links/widgets appear in the same order each time.
Information previously entered by the user must be auto-populated or available for selection when the same information is requested again in the same session/process (e.g., multi-step checkout forms).
<!-- Step 2: Billing address — pre-fill from Step 1 shipping -->
<fieldset>
<legend>Billing Address</legend>
<label>
<input type="checkbox" id="same-as-shipping" />
Same as shipping address
</label>
<!-- When checked: auto-populate billing fields from shipping fields -->
</fieldset>
Exceptions: Re-entering passwords for security confirmation, selecting items from a list.
Authentication processes MUST NOT require users to complete a cognitive function test (solve puzzle, identify images, remember/transcribe a code) unless an accessible alternative is provided.
Allowed alternatives:
<!-- GOOD: Allow paste in password fields -->
<input type="password" id="password" autocomplete="current-password" />
<!-- Do NOT add: onpaste="return false" -->
<!-- GOOD: Provide alternative to image CAPTCHA -->
<div role="group" aria-labelledby="captcha-label">
<span id="captcha-label">Verify you are human</span>
<img src="captcha.png" alt="CAPTCHA challenge" />
<input type="text" aria-describedby="captcha-label" />
<a href="?audio-captcha">Use audio CAPTCHA instead</a>
<a href="?email-login">Use email link instead</a>
</div>
Test: Identify all authentication steps. Verify no step requires a cognitive test without an alternative method.
No cognitive function test is required, even with alternatives provided.
Check all text meets WCAG contrast ratios:
Standards:
| 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 | - |
Tools:
Example:
/* ❌ BAD - Innsufficient 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 */
Ensure proper sscreen reader experience:
Alt Text for Images:
<!-- ❌ BAD - Missing or redundant alt -->
<img src="logo.png" />
<img src="decorative.png" alt="decorative image" />
<!-- ✅ GOOD -->
<img src="logo.png" alt="Company y Logo" />
<img src="decorative.png" alt="" role="presentation" />
ARIA Labels for Icon Buttons:
<!-- ❌ BAD - No label for screen readers -->
<button><i class="icon-delete"></i></button>
<!-- ✅ GOOD -->
<but tton aria-label="Delete item">
<i class="icon-delete" aria-hidden="true"></i>
</button>
Live Regions for Dynamic Content:
<!-- 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>
Ensure all form inputs are properly labeled and validated:
Requirements:
<label> elements<fieldset> and <legend> for grouped inputsaria-describedbyaria-required="true" or required attributeExample:
<!-- ✅ GOOD Form Structure -->
<form>
<fieldset>
<legend>Personal Information</legend>
<label for="name">Name (required)</label>
<input id="name" type="textt" 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>
Document findings with:
Skill({ skill: 'accessibility' });
Input: React component with custom modal Output:
Skill({ skill: 'accessibility', args: 'color-contrast' });
Input: CSS file with color definitions Output:
Skill({ skill: 'accessibility', args: 'forms' });
Input: Form component Output:
<best_practices>
<div> for everything (no semantic meaning)tabindex > 0 (disrupts natural tab order)| Anti-Pattern | Problem | Fix |
| ----------------------------------- | ------------------------------------------------ | -------------------------------------------- |
| <div onclick> | Not keyboard accessible | Use <button> |
| No alt text | Screen readers can't describe | Add meaningful alt attribute |
| Color-only info | Color blind users miss it | Add text/icons |
| No focus indicators | Users lost in navigation | Add :focus-visible styles |
| Auto-play media | Disruptive for screen readers | Add controls, pause option |
| <div> for everything | No semantic structure | Use semantic HTML |
| Sticky header without scroll-margin | Focus hidden behind sticky bar (2.4.11) | Add scroll-margin-top to :focus-visible |
| Drag-only sortable lists | Users with motor disabilities can't sort (2.5.7) | Add Up/Down buttons for each item |
| 16x16px icon buttons | Below 24x24 minimum target size (2.5.8) | Set min-width: 24px; min-height: 24px |
| CAPTCHA without alternative | Cognitive barrier to authentication (3.3.8) | Provide email magic link or passkey option |
| onpaste="return false" | Blocks password manager paste (3.3.8) | Remove paste block from password fields |
| Repeated form fields in checkout | Redundant data entry (3.3.7) | Auto-populate with previously entered values |
Before finalizing accessibility review:
WCAG 2.1 AA (existing requirements):
alt="" for decorative)WCAG 2.2 AA (new requirements — October 2023, ISO/IEC 40500:2025):
<button>, <nav>, <main>, etc.).outline: none without a replacement is an immediate WCAG failure. Keyboard users become completely lost..claude/rules/accessibility.md - Complete accessibility rulesBefore starting:
cat .claude/context/memory/learnings.md
Check for:
After completing:
.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.
tools
Comprehensive biosignal processing toolkit for analyzing physiological data including ECG, EEG, EDA, RSP, PPG, EMG, and EOG signals. Use this skill when processing cardiovascular signals, brain activity, electrodermal responses, respiratory patterns, muscle activity, or eye movements. Applicable for heart rate variability analysis, event-related potentials, complexity measures, autonomic nervous system assessment, psychophysiology research, and multi-modal physiological signal integration.
tools
Comprehensive toolkit for creating, analyzing, and visualizing complex networks and graphs in Python. Use when working with network/graph data structures, analyzing relationships between entities, computing graph algorithms (shortest paths, centrality, clustering), detecting communities, generating synthetic networks, or visualizing network topologies. Applicable to social networks, biological networks, transportation systems, citation networks, and any domain involving pairwise relationships.
data-ai
Molecular featurization for ML (100+ featurizers). ECFP, MACCS, descriptors, pretrained models (ChemBERTa), convert SMILES to features, for QSAR and molecular ML.
development
Run Python code in the cloud with serverless containers, GPUs, and autoscaling. Use when deploying ML models, running batch processing jobs, scheduling compute-intensive tasks, or serving APIs that require GPU acceleration or dynamic scaling.