Install
$ agentstack add skill-bbeierle12-skill-mcp-claude-form-accessibility ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Form Accessibility
WCAG 2.2 AA compliance patterns for forms. Ensures forms work for keyboard users, screen reader users, and users with cognitive or motor disabilities.
Quick Start
// Accessible form field pattern
{/* 1. Visible label (never placeholder-only) */}
Email
*
{/* 2. Hint text (separate from label) */}
We'll send your confirmation here
{/* 3. Input with full ARIA binding */}
{/* 4. Error message (announced by screen readers) */}
{hasError && (
Please enter a valid email address
)}
WCAG 2.2 Form Requirements
Critical Criteria
| Criterion | Level | Requirement | Implementation | |-----------|-------|-------------|----------------| | 1.3.1 Info & Relationships | A | Structure conveyed programmatically | `, , aria-describedby | | 1.3.5 Identify Input Purpose | AA | Input purpose identifiable | autocomplete attributes | | 2.1.1 Keyboard | A | All functionality via keyboard | Tab order, focus management | | 2.4.6 Headings & Labels | AA | Labels describe purpose | Descriptive, visible labels | | 2.4.11 Focus Not Obscured | AA | Focus not hidden by other content | Scroll behavior, sticky elements | | 2.5.8 Target Size | AA | 24×24px minimum touch target | Button/input sizing | | 3.3.1 Error Identification | A | Errors identified and described | aria-invalid`, error messages | | 3.3.2 Labels or Instructions | A | Labels provided | Visible labels, not just placeholders | | 3.3.3 Error Suggestion | AA | Suggestions for fixing errors | Actionable error messages | | 3.3.7 Redundant Entry | A | Don't re-ask for info already provided | Form state management | | 3.3.8 Accessible Authentication | AA | No cognitive function tests | No CAPTCHAs requiring text recognition |
New in WCAG 2.2 (October 2023)
2.4.11 Focus Not Obscured (AA)
/* Ensure focus is never hidden by sticky headers */
.sticky-header {
position: sticky;
top: 0;
}
input:focus {
/* Browser should scroll input into view above sticky elements */
scroll-margin-top: 80px; /* Height of sticky header */
}
2.5.8 Target Size (AA)
/* Minimum 24×24px touch targets */
button,
input[type="submit"],
input[type="checkbox"],
input[type="radio"] {
min-width: 24px;
min-height: 24px;
}
/* Better: 44×44px for comfortable touch */
.touch-friendly {
min-width: 44px;
min-height: 44px;
}
3.3.7 Redundant Entry (A)
// ❌ BAD: Asking for email twice
// ✅ GOOD: Ask once, show confirmation
Confirmation will be sent to: {email}
3.3.8 Accessible Authentication (AA)
// ❌ BAD: CAPTCHA requiring text recognition
// ✅ GOOD: Alternative verification methods
Send verification code to email
ARIA Patterns
Error Message Binding
// Pattern: aria-describedby links input to error
{hasError && (
{errorMessage}
)}
Multiple Descriptions
// Pattern: Combine hint + error in aria-describedby
Must be at least 8 characters
{hasError && {error}}
Required Fields
// Pattern: Announce required status
Name
*
{/* Visual indicator hidden from SR, aria-required announces it */}
// Alternative: Required in label (simpler)
Name (required)
Field Groups
// Pattern: fieldset + legend for related fields
Shipping Address
Street
City
Radio/Checkbox Groups
// Pattern: fieldset groups options, legend is the question
Preferred contact method
Email
Phone
Focus Management
Focus on First Error
// On form submit with errors, focus first invalid field
function handleSubmit(e: FormEvent) {
e.preventDefault();
const firstError = formRef.current?.querySelector('[aria-invalid="true"]');
if (firstError) {
(firstError as HTMLElement).focus();
return;
}
// Submit if valid
submitForm();
}
Focus on Step Change (Multi-step)
// Move focus to step heading when changing steps
function goToStep(stepNumber: number) {
setCurrentStep(stepNumber);
// Wait for render, then focus
requestAnimationFrame(() => {
const heading = document.getElementById(`step-${stepNumber}-heading`);
heading?.focus();
});
}
// Heading must be focusable
Shipping Address
Skip Links
// Allow skipping to form
Skip to form
{/* Form content */}
// CSS for skip link
.skip-link {
position: absolute;
top: -40px;
left: 0;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
Focus Trap (Modals)
// Keep focus within modal form
function FocusTrap({ children }) {
const trapRef = useRef(null);
useEffect(() => {
const trap = trapRef.current;
if (!trap) return;
const focusableElements = trap.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
function handleKeyDown(e: KeyboardEvent) {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
trap.addEventListener('keydown', handleKeyDown);
firstElement?.focus();
return () => trap.removeEventListener('keydown', handleKeyDown);
}, []);
return {children};
}
Color & Contrast
Error States (Colorblind-Safe)
/* ❌ BAD: Color only */
.error {
border-color: red;
}
/* ✅ GOOD: Color + icon + text */
.field-error {
border-color: #dc2626;
border-width: 2px;
}
.field-error::after {
content: "";
background-image: url("data:image/svg+xml,..."); /* Error icon */
}
.error-message {
color: #dc2626;
font-weight: 500;
}
.error-message::before {
content: "⚠ "; /* Text indicator */
}
Focus Indicators
/* Focus must have 3:1 contrast ratio */
input:focus {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
/* For dark backgrounds */
input:focus {
outline: 2px solid #60a5fa;
outline-offset: 2px;
}
/* Never remove outline without replacement */
/* ❌ BAD */
input:focus {
outline: none;
}
/* ✅ GOOD: Custom focus style */
input:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.5);
}
Validation States (Colorblind-Friendly)
// Use icons + text, not just color
function ValidationIndicator({ state }: { state: 'valid' | 'invalid' | 'idle' }) {
if (state === 'idle') return null;
return (
{state === 'valid' && '✓'}
{state === 'invalid' && '✗'}
);
}
Keyboard Navigation
Tab Order
// Natural tab order (no positive tabindex needed)
// ❌ BAD: Manual tab order
// ✅ GOOD: Natural DOM order
{/* tabIndex implicitly 0 */}
Escape Key Handling
// Allow Escape to close dropdowns, cancel modals
function Modal({ onClose, children }) {
useEffect(() => {
function handleEscape(e: KeyboardEvent) {
if (e.key === 'Escape') {
onClose();
}
}
document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}, [onClose]);
return {children};
}
Enter to Submit
// Forms submit on Enter by default
// For buttons that shouldn't submit:
Add Item
// For preventing Enter submit on specific fields:
{
if (e.key === 'Enter') {
e.preventDefault();
// Do something else
}
}}
/>
Live Regions
Error Announcements
// Announce errors when they appear
{errorCount > 0 && `${errorCount} errors in form`}
// Or use role="alert" for immediate announcement
{hasError && (
{errorMessage}
)}
Loading States
// Announce loading state
{isLoading ? (
<>
Loading...
Submitting form, please wait
) : (
'Submit'
)}
// Or use aria-busy
{/* ... */}
Success Messages
// Announce successful submission
{isSuccess && (
Form submitted successfully!
)}
Screen Reader Only Content
/* Visually hidden but announced by screen readers */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Allow focus for skip links */
.sr-only-focusable:focus {
position: static;
width: auto;
height: auto;
overflow: visible;
clip: auto;
white-space: normal;
}
Testing Accessibility
Automated Tools
# axe-core (recommended)
npm install @axe-core/react
# In development
import React from 'react';
import ReactDOM from 'react-dom';
import axe from '@axe-core/react';
if (process.env.NODE_ENV !== 'production') {
axe(React, ReactDOM, 1000);
}
Manual Testing Checklist
- Keyboard only: Can you complete the form using only Tab, Enter, Space, and Arrow keys?
- Screen reader: Does VoiceOver/NVDA announce labels, errors, and required status?
- Zoom 200%: Is the form usable at 200% browser zoom?
- High contrast: Is everything visible in Windows High Contrast mode?
- Focus visible: Can you always see which element is focused?
Testing Script
// Automated accessibility test
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('form is accessible', async () => {
const { container } = render();
const results = await axe(container);
expect(results).toHaveNoViolations();
});
test('error state is accessible', async () => {
const { container } = render();
// Trigger error
fireEvent.blur(screen.getByLabelText(/email/i));
const results = await axe(container);
expect(results).toHaveNoViolations();
});
File Structure
form-accessibility/
├── SKILL.md
├── references/
│ ├── wcag-2.2-forms.md # Full WCAG criteria breakdown
│ └── aria-patterns.md # Complete ARIA reference
└── scripts/
├── aria-form-wrapper.tsx # Automatic ARIA binding
├── focus-manager.ts # Focus trap, error focus
├── error-announcer.ts # Live region management
└── accessibility-validator.ts # Runtime a11y checks
Reference
references/wcag-2.2-forms.md— Complete WCAG 2.2 criteria for formsreferences/aria-patterns.md— Detailed ARIA implementation patterns
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Bbeierle12
- Source: Bbeierle12/Skill-MCP-Claude
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.