Use when implementing any UI - verifies accessibility compliance through automated testing (axe-core), keyboard navigation, screen reader verification, and Lighthouse audits; legally required and ensures inclusive user experience
Automated accessibility testing with axe-core, keyboard navigation, and Lighthouse audits when implementing or modifying UI. Verifies WCAG compliance before marking work complete.
/plugin marketplace add samjhecht/wrangler/plugin install wrangler@samjhecht-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
example.tsMANDATORY: When using this skill, announce it at the start with:
š§ Using Skill: frontend-accessibility-verification | [brief purpose based on context]
Example:
š§ Using Skill: frontend-accessibility-verification | [Provide context-specific example of what you're doing]
This creates an audit trail showing which skills were applied during the session.
Accessibility testing ensures UI is usable by everyone, including users with disabilities. It's both a legal requirement (WCAG compliance) and fundamental to good UX.
When to use this skill:
NO UI SHIP WITHOUT ACCESSIBILITY VERIFICATION
If you created or modified UI:
Legal requirement:
Better UX for everyone:
Automated testing catches ~57% of issues:
Run axe-core on all UI components:
import { injectAxe, checkA11y } from 'axe-playwright';
test('component is accessible', async ({ page }) => {
await page.goto('/checkout');
// Inject axe-core into page
await injectAxe(page);
// Run accessibility audit
await checkA11y(page, null, {
detailedReport: true,
detailedReportOptions: {
html: true,
},
});
});
import 'cypress-axe';
describe('Accessibility', () => {
it('has no a11y violations', () => {
cy.visit('/checkout');
cy.injectAxe();
cy.checkA11y();
});
});
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('has no accessibility violations', async () => {
const { container } = render(<CheckoutForm />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Expected result: 0 violations
ā
PASS: 0 accessibility violations found
ā FAIL: Found violations - must fix before proceeding
Test ALL interactive elements are keyboard accessible:
Tab Navigation:
Enter/Space Activation:
Escape Key:
Arrow Keys (for dropdowns/menus):
test('can navigate form with keyboard', async ({ page }) => {
await page.goto('/checkout');
// Tab to first input
await page.keyboard.press('Tab');
await expect(page.locator('[name="email"]')).toBeFocused();
// Tab to second input
await page.keyboard.press('Tab');
await expect(page.locator('[name="password"]')).toBeFocused();
// Tab to submit button
await page.keyboard.press('Tab');
await expect(page.locator('button[type="submit"]')).toBeFocused();
// Activate with Enter
await page.keyboard.press('Enter');
// Verify form submitted
await expect(page.locator('[data-testid="success"]')).toBeVisible();
});
Verify all UI elements have proper ARIA labels:
test('all interactive elements have accessible names', async ({ page }) => {
await page.goto('/checkout');
// Get all interactive elements
const buttons = page.locator('button, [role="button"]');
const links = page.locator('a');
const inputs = page.locator('input, textarea, select');
// Verify each has accessible name
for (const button of await buttons.all()) {
const name = await button.getAttribute('aria-label')
|| await button.textContent();
expect(name).toBeTruthy();
}
for (const input of await inputs.all()) {
const label = await input.getAttribute('aria-label')
|| await page.locator(`label[for="${await input.getAttribute('id')}"]`).textContent();
expect(label).toBeTruthy();
}
});
Mac (VoiceOver):
1. Press Cmd+F5 to enable VoiceOver
2. Press Control+Option+Right Arrow to navigate
3. Verify all elements announced correctly
4. Press Cmd+F5 to disable VoiceOver
Windows (NVDA):
1. Launch NVDA
2. Press Down Arrow to navigate
3. Verify all elements announced correctly
What to verify:
Run Lighthouse audit (target: 95%+ score):
# Install lighthouse
npm install -g lighthouse
# Run audit
lighthouse http://localhost:3000/checkout --only-categories=accessibility --view
1. Open DevTools (F12)
2. Click "Lighthouse" tab
3. Select "Accessibility" category
4. Click "Analyze page load"
5. Review score and issues
Target score: 95 or higher
ā
PASS: Score 95-100
ā ļø WARNING: Score 90-94 (fix issues if possible)
ā FAIL: Score <90 (must fix before proceeding)
Verify compliance with WCAG 2.1 Level AA:
<html lang="en">)// Works with Playwright, Selenium, Puppeteer
test('component is accessible', async ({ page }) => {
// Navigate to component
await mount('<custom-dropdown></custom-dropdown>');
// Inject axe-core
await injectAxe(page);
// Check baseline state
await checkA11y(page);
// Check interactive state
await page.click('[role="button"]');
await checkA11y(page);
});
test('checkout flow is accessible', async ({ page }) => {
await injectAxe(page);
// Check each step
await page.goto('/checkout');
await checkA11y(page);
await page.fill('[name="email"]', 'test@example.com');
await checkA11y(page);
await page.click('text=Place Order');
await checkA11y(page);
});
// Storybook 9+ has built-in a11y testing
export default {
component: Dropdown,
tags: ['autodocs'],
// Automatic accessibility tests for all stories
};
BEFORE claiming UI work complete:
If ANY checkbox unchecked: UI is NOT accessible. Fix before claiming complete.
When claiming UI work complete, provide:
Accessibility test results:
$ npm test -- checkout.a11y.test.ts
PASS tests/checkout.a11y.test.ts
ā has no accessibility violations (1234ms)
axe-core violations: 0
WCAG 2.1 Level AA: PASS
Exit code: 0
Keyboard navigation verification:
ā Tab through all elements: PASS
ā Focus visible: PASS
ā Enter/Space activates buttons: PASS
ā Escape closes modal: PASS
ā No keyboard traps: PASS
All keyboard navigation working correctly.
Lighthouse accessibility audit:
[Screenshot showing score]
Score: 97/100
Result: PASS (ā„95 required)
If you catch yourself:
<div onclick>)THEN:
<!-- ā BAD: Icon button with no label -->
<button><img src="close.svg" /></button>
<!-- ā
GOOD: Aria-label provided -->
<button aria-label="Close"><img src="close.svg" alt="" /></button>
<!-- ā BAD: Div as button (not keyboard accessible) -->
<div onclick="submit()">Submit</div>
<!-- ā
GOOD: Use semantic button -->
<button onclick="submit()">Submit</button>
<!-- ā BAD: No label -->
<input type="text" placeholder="Email" />
<!-- ā
GOOD: Explicit label -->
<label for="email">Email</label>
<input id="email" type="text" />
/* ā BAD: Low contrast (2:1) */
.text { color: #777; background: #fff; }
/* ā
GOOD: Sufficient contrast (4.5:1+) */
.text { color: #333; background: #fff; }
// ā BAD: Modal doesn't trap focus, no Escape key
<div class="modal">...</div>
// ā
GOOD: Focus management + Escape key
test('modal is accessible', async ({ page }) => {
await page.click('[data-testid="open-modal"]');
// Focus moves to modal
await expect(page.locator('.modal button:first-child')).toBeFocused();
// Tab stays within modal
await page.keyboard.press('Tab');
await expect(page.locator('.modal')).toContainElement(page.locator(':focus'));
// Escape closes modal
await page.keyboard.press('Escape');
await expect(page.locator('.modal')).not.toBeVisible();
});
Combines with:
| Rationalization | Counter |
|---|---|
| "We'll add accessibility later" | Retrofitting accessibility is expensive. Do it now. |
| "It's just an internal tool" | Internal users have disabilities too. Legal requirement applies. |
| "Automated tests aren't perfect" | They catch 57% of issues. Better than 0%. Run them. |
| "Keyboard testing is tedious" | Takes 2 minutes. Excludes users if skipped. Required. |
| "Screen readers are rare" | ~2% of users. Legal liability. Non-negotiable. |
Accessibility Regression Testing (2024 innovation):
// Sets baseline for pre-existing violations
// Prevents NEW accessibility bugs
// Integrates with Storybook + Chromatic
// Visual + a11y testing in one workflow
Benefits:
Agent: "I'm implementing a checkout form component."
[Uses frontend-accessibility-verification skill]
1. Write component with semantic HTML (<button>, <label>, etc.)
2. Write axe-core test expecting 0 violations
3. Run test ā FAIL (missing aria-label on submit button)
4. Add aria-label to button
5. Run test ā PASS (0 violations)
6. Test keyboard navigation:
- Tab through inputs ā Works
- Enter on submit ā Works
- All focus visible ā Works
7. Run Lighthouse audit ā Score: 98
8. Provide evidence:
- axe-core: 0 violations
- Keyboard: All working
- Lighthouse: 98/100
"Checkout form complete. Accessibility verified:
- axe-core: 0 violations
- Keyboard navigation: Fully accessible
- Lighthouse score: 98/100
- Screen reader compatible"
Remember: NO UI SHIP WITHOUT ACCESSIBILITY VERIFICATION. Legal requirement, not optional.
Master authentication and authorization patterns including JWT, OAuth2, session management, and RBAC to build secure, scalable access control systems. Use when implementing auth systems, securing APIs, or debugging security issues.