a11y-patternslisted
Install: claude install-skill Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack
# Accessibility Patterns
## ARIA Roles & Attributes
```html
<!-- Landmark roles -->
<header role="banner">
<nav aria-label="Main navigation">
<main role="main">
<aside aria-label="Related articles">
<footer role="contentinfo">
<!-- Button vs div -->
<!-- WRONG: -->
<div class="btn" onclick="submit()">Submit</div>
<!-- RIGHT: -->
<button type="submit">Submit</button>
<!-- Icon button — always needs label -->
<button aria-label="Close dialog">
<svg aria-hidden="true" focusable="false">...</svg>
</button>
<!-- Toggle button -->
<button aria-pressed="false" id="theme-toggle">Dark mode</button>
```
## Live Regions
```html
<!-- Announce dynamic content to screen readers -->
<div aria-live="polite" aria-atomic="true" class="sr-only" id="status"></div>
<!-- Urgent announcements (interrupts) -->
<div role="alert">Your session expires in 5 minutes.</div>
```
```js
function announce(message, urgency = 'polite') {
const el = document.getElementById('status');
el.setAttribute('aria-live', urgency);
el.textContent = '';
requestAnimationFrame(() => { el.textContent = message; });
}
```
## Keyboard Navigation
```js
// Roving tabindex for widget internals (e.g., toolbar, listbox)
class RovingTabindex {
constructor(container) {
this.items = [...container.querySelectorAll('[role="option"]')];
this.current = 0;
this.items[0].tabIndex = 0;
this.items.slice(1).forEach(el => (el.tabIndex = -1));
container.addEventListener('keydown', this.#onKey.bind(this))