Accessible Forms with Claude Code (2026)
Building accessible forms requires more than semantic HTML Proper validation error handling determines whether users with disabilities can successfully complete your forms. Claude Code provides several skills that streamline accessible form development, from generating compliant markup to implementing thorough validation logic.
This guide covers practical approaches to accessible form validation using Claude Code skills, with code examples you can apply immediately.
Understanding WCAG Form Accessibility Requirements
Web Content Accessibility Guidelines (WCAG) specify several requirements for form validation:
- Error messages must be programmatically associated with form fields
- Users must be notified of errors in an accessible manner
- Error identification should be clear and specific
- Input purpose should be programmatically determinable
The frontend-design skill understands these requirements and generates form components with proper ARIA attributes, labels, and error message containers built in.
Setting Up Accessible Form Markup
Start with semantic HTML that supports screen readers. The frontend-design skill generates forms with proper structure:
<form novalidate>
<div role="group" aria-labelledby="email-group-label">
<label for="email">
Email address
<span aria-required="true">*</span>
</label>
<input
type="email"
id="email"
name="email"
required
aria-describedby="email-error"
autocomplete="email"
>
<span
id="email-error"
role="alert"
aria-live="polite"
class="error-message"
></span>
</div>
</form>
Key accessibility attributes include aria-describedby linking the input to its error message, aria-live ensuring dynamic errors are announced, and proper label association.
Implementing Validation with the tdd Skill
Use the tdd skill to develop validation logic test-first. This ensures your error handling works correctly for all users:
// Request the tdd skill to write validation tests
"Write tests for an email validation function that checks format,
returns specific error messages, and handles edge cases like empty
input vs invalid format"
The tdd skill generates comprehensive test cases covering:
- Valid email formats
- Common typos (.gmai.com instead of .gmail.com)
- Empty field validation
- Real-time vs submit-time validation
- Error message content and structure
Real-Time Validation Patterns
Implementing real-time validation requires balancing usability with accessibility. The frontend-design skill suggests these patterns:
const validateField = async (field, value) => {
const errorElement = document.getElementById(`${field}-error`);
const inputElement = document.getElementById(field);
// Clear previous error
errorElement.textContent = '';
inputElement.setAttribute('aria-invalid', 'false');
const result = await validate(value, field);
if (result.error) {
errorElement.textContent = result.message;
inputElement.setAttribute('aria-invalid', 'true');
// Announce error to screen readers
errorElement.focus();
}
return !result.error;
};
This pattern updates both visual error display and ARIA attributes, ensuring screen reader users receive the same information as visual users.
Custom Error Announcements with ARIA
For sophisticated error announcement strategies, go beyond a single aria-live container. Rather than relying solely on aria-live, consider these approaches:
const announceError = (message, containerId) => {
const container = document.getElementById(containerId);
// Create a polite announcement after current speech
const announcement = document.createElement('div');
announcement.setAttribute('role', 'status');
announcement.setAttribute('aria-live', 'polite');
announcement.setAttribute('aria-atomic', 'true');
announcement.className = 'sr-only';
announcement.textContent = message;
container.appendChild(announcement);
// Remove after announcement
setTimeout(() => announcement.remove(), 1000);
};
This technique provides clear feedback without interrupting the user’s current navigation.
Form-Level Validation Errors
When validation fails on multiple fields, communicate all errors clearly. The frontend-design skill generates form-level error summaries:
<div
id="form-errors"
role="alert"
aria-labelledby="form-errors-heading"
class="error-summary"
>
<h2 id="form-errors-heading">Please correct the following errors</h2>
<ul>
<li><a href="#email">Enter a valid email address</a></li>
<li><a href="#password">Password must be at least 8 characters</a></li>
</ul>
</div>
This pattern allows keyboard users to jump directly to the first error, with each list item linking to the problematic field.
Validation for Specific Input Types
Different input types require different validation strategies. The pdf skill can generate comprehensive validation documentation for your team:
"Create a validation reference document showing error handling
patterns for email, phone, credit card, date, and URL inputs
with WCAG compliance notes"
Common patterns include:
- Email: Format validation with domain suggestions
- Phone: Flexible format matching for international numbers
- Date: Calendar picker with keyboard navigation
- Credit Card: Luhn algorithm validation
- URL: Protocol and structure validation
Error Prevention and User Assistance
Beyond validation, accessible forms help users avoid errors through:
- Input hints: Placeholder text with
aria-placeholder(not a replacement for labels) - Required field indicators: Visual asterisk with
aria-required="true" - Character counts: For fields with length limits
- Real-time feedback: As users type, not just on blur
The supermemory skill helps maintain consistency across your forms by remembering patterns your team has approved:
"Where did we document our required field validation approach?"
Testing Accessibility
Validate your accessible forms using multiple methods:
- Keyboard-only navigation: Tab through all fields and verify focus order
- Screen reader testing: Use NVDA, VoiceOver, or JAWS to experience the form
- Automated tools: axe-core, WAVE, or Lighthouse
- User testing: Include users with disabilities when possible
The tdd skill can generate accessibility-focused test cases:
"Write tests that verify error messages are announced to screen
readers, focus moves to the first error field, and all form
controls are keyboard accessible"
Handling Multi-Step Form Accessibility
Multi-step forms (wizards) introduce additional accessibility challenges beyond single-form validation. Users need to understand their progress, navigate between steps, and recover from errors in earlier steps without losing data.
Claude Code’s frontend-design skill generates multi-step form patterns with these accessibility requirements built in. The key elements:
<!-- Progress indicator with accessible labeling -->
<nav aria-label="Form progress">
<ol>
<li aria-current="step">
<span class="step-number" aria-hidden="true">1</span>
<span class="step-label">Personal Information</span>
</li>
<li>
<span class="step-number" aria-hidden="true">2</span>
<span class="step-label">Account Details</span>
</li>
<li>
<span class="step-number" aria-hidden="true">3</span>
<span class="step-label">Review</span>
</li>
</ol>
</nav>
<!-- Dynamic heading that updates per step -->
<h1 id="step-heading" aria-live="polite">
Step 1 of 3: Personal Information
</h1>
The aria-current="step" attribute tells screen readers which step the user is currently on. The aria-live="polite" on the heading announces step transitions without interrupting the user’s current action.
For validating across steps, maintain a validation state object that tracks errors per step. When a user tries to advance, validate the current step and announce any errors:
class MultiStepFormValidator {
constructor(totalSteps) {
this.errors = Array.from({ length: totalSteps }, () => ({}));
this.currentStep = 0;
}
validateStep(stepIndex, formData) {
const stepErrors = {};
const validators = this.getValidatorsForStep(stepIndex);
for (const [field, validator] of Object.entries(validators)) {
const result = validator(formData[field]);
if (!result.valid) {
stepErrors[field] = result.message;
}
}
this.errors[stepIndex] = stepErrors;
return Object.keys(stepErrors).length === 0;
}
announceErrors(stepIndex) {
const errors = this.errors[stepIndex];
const errorCount = Object.keys(errors).length;
if (errorCount === 0) return;
// Create announcement
const announcement = `${errorCount} error${errorCount > 1 ? 's' : ''} found. ` +
Object.values(errors).join('. ');
// Announce via live region
const liveRegion = document.getElementById('form-announcements');
liveRegion.textContent = ''; // Clear first to ensure re-announcement
requestAnimationFrame(() => {
liveRegion.textContent = announcement;
});
}
}
The requestAnimationFrame trick ensures the DOM change triggers the screen reader’s live region announcement even when the text was previously the same string.
Automated Accessibility Testing for Forms
Manual testing with screen readers is essential but time-consuming. Automated testing catches the most common accessibility violations and runs on every commit, preventing regressions.
The tdd skill generates accessible form tests using jest-dom and @testing-library/user-event, which simulate real keyboard interactions:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { axe, toHaveNoViolations } from 'jest-axe';
import { LoginForm } from '../LoginForm';
expect.extend(toHaveNoViolations);
describe('LoginForm accessibility', () => {
it('has no axe violations', async () => {
const { container } = render(<LoginForm />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it('announces validation errors to screen readers', async () => {
const user = userEvent.setup();
render(<LoginForm />);
// Submit without filling fields
await user.click(screen.getByRole('button', { name: /submit/i }));
// Error should be in an aria-live region
const emailError = screen.getByRole('alert');
expect(emailError).toHaveTextContent(/email is required/i);
});
it('associates errors with their fields', async () => {
const user = userEvent.setup();
render(<LoginForm />);
await user.click(screen.getByRole('button', { name: /submit/i }));
const emailInput = screen.getByLabelText(/email/i);
const errorId = emailInput.getAttribute('aria-describedby');
// The referenced error element should exist and contain the error text
const errorElement = document.getElementById(errorId);
expect(errorElement).toBeInTheDocument();
expect(errorElement).toHaveTextContent(/.+/); // Has some error text
});
it('moves focus to first error on submit', async () => {
const user = userEvent.setup();
render(<LoginForm />);
await user.click(screen.getByRole('button', { name: /submit/i }));
// Focus should move to first invalid field
const emailInput = screen.getByLabelText(/email/i);
expect(emailInput).toHaveFocus();
});
});
Running these tests in CI ensures that accessible form behavior is maintained as the component evolves. The jest-axe integration catches WCAG violations automatically, while the custom tests verify specific accessible interaction patterns that axe cannot detect through static analysis alone.
Summary
Accessible form validation requires attention to both implementation and user experience. Use these key practices:
- Associate error messages with inputs using
aria-describedby - Announce errors with
aria-liveregions - Provide specific, actionable error messages
- Implement form-level summaries for multiple errors
- Test with actual assistive technologies
Invoke /frontend-design to generate accessible form components, /tdd to develop validation logic test-first, and /supermemory to maintain consistency across your forms. The pdf skill helps create team documentation, while the docx skill generates formal specifications for accessibility requirements.
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Best Claude Code Skills for Frontend Development. UI generation, testing, and component patterns
- Automated Testing Pipeline with Claude TDD Skill. Test-driven development workflows
- Claude Skills for Code Review Automation. Automated accessibility checks
*Built by theluckystrike. More at zovo.one *
Find the right skill → Browse 155+ skills in our Skill Finder.