auditing-accessibility-wcag

Accessibility Auditor (WCAG 2.1)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "auditing-accessibility-wcag" with this command: npx skills add wesleysmits/agent-skills/wesleysmits-agent-skills-auditing-accessibility-wcag

Accessibility Auditor (WCAG 2.1)

When to use this skill

  • User asks about accessibility or a11y

  • User mentions WCAG or compliance levels

  • User wants to audit a component or page

  • User asks about aria labels or roles

  • User needs accessible alternative patterns

Workflow

  • Identify scope (component, page, site)

  • Run automated audits

  • Review manual checkpoints

  • Categorize violations by severity

  • Provide remediation examples

  • Validate fixes

Instructions

Step 1: Run Automated Audits

axe-core (CLI):

npm install -D @axe-core/cli npx axe http://localhost:3000 --exit

Lighthouse accessibility audit:

npx lighthouse http://localhost:3000 --only-categories=accessibility --output=json

ESLint accessibility plugin:

npm install -D eslint-plugin-jsx-a11y

// eslint.config.js import jsxA11y from "eslint-plugin-jsx-a11y";

export default [jsxA11y.flatConfigs.recommended];

Step 2: WCAG 2.1 AA Checklist

Principle Guideline Common Issues

Perceivable 1.1 Text Alternatives Missing alt text

Perceivable 1.3 Adaptable Improper heading order

Perceivable 1.4 Distinguishable Low color contrast

Operable 2.1 Keyboard No focus styles

Operable 2.4 Navigable Missing skip links

Understandable 3.1 Readable Missing lang attribute

Understandable 3.2 Predictable Unexpected focus changes

Robust 4.1 Compatible Invalid HTML, missing roles

Step 3: Common Violations & Fixes

Missing alt text:

// ❌ Violation <img src="/hero.jpg" />

// ✅ Fixed - Informative image <img src="/hero.jpg" alt="Team collaborating in modern office" />

// ✅ Fixed - Decorative image <img src="/divider.svg" alt="" role="presentation" />

Low color contrast (4.5:1 minimum for AA):

/* ❌ Violation - 2.5:1 ratio */ .text-muted { color: #999999; background: #ffffff; }

/* ✅ Fixed - 4.6:1 ratio */ .text-muted { color: #767676; background: #ffffff; }

Missing form labels:

// ❌ Violation <input type="email" placeholder="Email" />

// ✅ Fixed - Visible label <label htmlFor="email">Email</label> <input id="email" type="email" />

// ✅ Fixed - Visually hidden label <label htmlFor="email" className="sr-only">Email</label> <input id="email" type="email" placeholder="Email" />

Missing focus indicators:

/* ❌ Violation */ button:focus { outline: none; }

/* ✅ Fixed */ button:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

Improper heading hierarchy:

// ❌ Violation - Skips h2 <h1>Page Title</h1> <h3>Section Title</h3>

// ✅ Fixed <h1>Page Title</h1> <h2>Section Title</h2>

Non-semantic buttons:

// ❌ Violation <div onClick={handleClick}>Click me</div>

// ✅ Fixed <button type="button" onClick={handleClick}>Click me</button>

Missing skip link:

// ✅ Add at top of page <a href="#main-content" className="skip-link"> Skip to main content </a>

// ... header/nav ...

<main id="main-content"> {/* Page content */} </main>

.skip-link { position: absolute; left: -9999px; z-index: 999; padding: 1rem; background: var(--color-background); color: var(--color-text); }

.skip-link:focus { left: 1rem; top: 1rem; }

Step 4: Interactive Component Patterns

Accessible modal:

import { useEffect, useRef } from "react";

interface ModalProps { isOpen: boolean; onClose: () => void; title: string; children: React.ReactNode; }

export function Modal({ isOpen, onClose, title, children }: ModalProps) { const closeButtonRef = useRef<HTMLButtonElement>(null); const modalRef = useRef<HTMLDivElement>(null);

useEffect(() => { if (isOpen) { closeButtonRef.current?.focus(); document.body.style.overflow = "hidden"; } return () => { document.body.style.overflow = ""; }; }, [isOpen]);

// Trap focus inside modal const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Escape") onClose(); if (e.key === "Tab") { const focusable = modalRef.current?.querySelectorAll<HTMLElement>( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', ); if (!focusable?.length) return;

  const first = focusable[0];
  const last = focusable[focusable.length - 1];

  if (e.shiftKey &#x26;&#x26; document.activeElement === first) {
    e.preventDefault();
    last.focus();
  } else if (!e.shiftKey &#x26;&#x26; document.activeElement === last) {
    e.preventDefault();
    first.focus();
  }
}

};

if (!isOpen) return null;

return ( <div role="dialog" aria-modal="true" aria-labelledby="modal-title" ref={modalRef} onKeyDown={handleKeyDown} > <div className="modal-backdrop" onClick={onClose} aria-hidden="true" /> <div className="modal-content"> <h2 id="modal-title">{title}</h2> {children} <button ref={closeButtonRef} onClick={onClose} aria-label="Close modal"> × </button> </div> </div> ); }

Accessible dropdown menu:

import { useState, useRef } from "react";

export function Dropdown({ label, items }: { label: string; items: string[] }) { const [isOpen, setIsOpen] = useState(false); const [activeIndex, setActiveIndex] = useState(-1); const buttonRef = useRef<HTMLButtonElement>(null);

const handleKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case "ArrowDown": e.preventDefault(); setActiveIndex((i) => Math.min(i + 1, items.length - 1)); break; case "ArrowUp": e.preventDefault(); setActiveIndex((i) => Math.max(i - 1, 0)); break; case "Escape": setIsOpen(false); buttonRef.current?.focus(); break; case "Enter": case " ": if (!isOpen) { setIsOpen(true); setActiveIndex(0); } break; } };

return ( <div onKeyDown={handleKeyDown}> <button ref={buttonRef} aria-expanded={isOpen} aria-haspopup="listbox" onClick={() => setIsOpen(!isOpen)} > {label} </button> {isOpen && ( <ul role="listbox" aria-activedescendant={item-${activeIndex}}> {items.map((item, i) => ( <li key={item} id={item-${i}} role="option" aria-selected={i === activeIndex} > {item} </li> ))} </ul> )} </div> ); }

Step 5: Testing Tools

Screen reader testing:

  • macOS: VoiceOver (Cmd+F5)

  • Windows: NVDA (free) or JAWS

  • Browser: ChromeVox extension

Browser extensions:

  • axe DevTools

  • WAVE Evaluation Tool

  • Accessibility Insights

Keyboard testing checklist:

  • All interactive elements reachable via Tab

  • Focus order matches visual order

  • Focus visible on all elements

  • Escape closes modals/dropdowns

  • Arrow keys navigate within components

Step 6: Audit Report Template

Accessibility Audit Report

URL: https://example.com Date: 2026-01-18 Standard: WCAG 2.1 AA Tools: axe-core, Lighthouse, manual testing

Summary

SeverityCount
Critical2
Serious5
Moderate8
Minor3

Critical Issues

1. Images missing alt text

  • WCAG: 1.1.1 Non-text Content
  • Location: Homepage hero, product cards
  • Impact: Screen reader users cannot understand image content
  • Fix: Add descriptive alt text to all informative images

2. Form inputs missing labels

  • WCAG: 1.3.1 Info and Relationships
  • Location: Contact form, search box
  • Impact: Users cannot identify form field purpose
  • Fix: Associate &#x3C;label> with each input via for/id

Recommendations

  1. Add eslint-plugin-jsx-a11y to catch issues during development
  2. Include accessibility testing in CI pipeline
  3. Train team on keyboard navigation testing

Validation

Before completing:

  • axe-core reports zero violations

  • Lighthouse accessibility score ≥ 90

  • Keyboard navigation works throughout

  • Screen reader announces content correctly

  • Color contrast meets 4.5:1 (text) / 3:1 (large text)

  • Focus is visible on all interactive elements

Error Handling

  • False positives: Some automated tools flag valid patterns; verify manually.

  • Dynamic content: Re-run audits after JavaScript loads; use axe-core in tests.

  • Third-party widgets: Document inaccessible third-party components for vendor follow-up.

  • Complex widgets: Reference WAI-ARIA Authoring Practices for correct patterns.

Resources

  • WCAG 2.1 Quick Reference

  • WAI-ARIA Authoring Practices

  • axe-core Rules

  • A11y Project Checklist

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Security

detecting-security-vulnerabilities

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

writing-product-descriptions

No summary provided by upstream source.

Repository SourceNeeds Review
Research

researching-seo-keywords

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

writing-press-releases

No summary provided by upstream source.

Repository SourceNeeds Review