skills / design

fixing-accessibility

what it does

Audit and fix HTML accessibility issues including ARIA labels, keyboard navigation, focus management, color contrast, and form errors. Use when adding interactive controls, forms, dialogs, or reviewing WCAG compliance.

Verified Before and after: 2 models 1 try per case View source Download .zip

Compare the answers

Answers from Claude Sonnet 5.5 and GPT-6.1 Sol · try 1 of 1

Same request, same model, once with the skill and once without. Read both and judge for yourself.

“Our signup form fails accessibility review. What needs fixing?”

Source material given with the request

<form> <input type="email" placeholder="Email *"> <span class="error" style="color:red">Enter a valid email</span> <input type="password" placeholder="Password *"> <span class="hint">At least 12 characters</span> <button type="submit" disabled>Create account</button> </form>
with the skill
## Violations **1. Inputs have no accessible name (critical)** ```html <input type="email" placeholder="Email *"> <input type="password" placeholder="Password *"> ``` - **Why:** Placeholders aren't reliable labels. They vanish on input, often have poor contrast, and aren't consistently announced. - **Fix:** Add a visible `<label>` tied to each input with `for`/`id`. **2. Required state is conveyed only by "*" in the placeholder (high)** - **Why:** Screen readers won't reliably announce it, and it disappears once the user types. - **Fix:** Add `required` and put the marker in the label. Hide a decorative asterisk with `aria-hidden`. **3. Error message isn't linked to the field (high)** ```html <span class="error" style="color:red">Enter a valid email</span> ``` - **Why:** Screen reader users won't know the error belongs to the email field or that the field is invalid. - **Fix:** Give the error an `id`, add `aria-describedby` and `aria-invalid="true"` on the input. Render the error only when it applies, and use `role="alert"` (or an `aria-live` region) so it's announced when it appears.
Show the rest (57 more lines)
**4. Error relies on color alone, and red may fail contrast (medium)** - **Why:** Users who can't distinguish red won't notice it, and pure `red` (#f00) on white is about 4:1, below the 4.5:1 minimum. - **Fix:** Add non-color cues such as an icon or "Error:" prefix text, and use a darker red like `#b00020`. Move the styling into the `.error` class instead of inline. **5. Hint text isn't associated with the password input (high)** ```html <span class="hint">At least 12 characters</span> ``` - **Why:** The password requirement isn't announced when the field is focused. - **Fix:** Give the hint an `id` and reference it in `aria-describedby`. **6. Submit button is disabled with no explanation (high)** ```html <button type="submit" disabled>Create account</button> ``` - **Why:** Disabled buttons are skipped by Tab, so keyboard and screen reader users can't discover them or learn why the form can't be submitted. - **Fix:** Keep the button enabled. Validate on submit, show errors linked to the fields, and move focus to the first invalid field. If it must stay disabled, use `aria-disabled="true"` (still focusable, block the submit in JS) and add visible text explaining what's missing. **7. Missing autocomplete (medium)** - **Why:** Without `autocomplete`, password managers and autofill can't identify the fields (WCAG 1.3.5). - **Fix:** Use `autocomplete="email"` and `autocomplete="new-password"`, and add `name` attributes. ## Minimal fixed version ```html <form novalidate> <div> <label for="email">Email <span aria-hidden="true">*</span></label> <input id="email" name="email" type="email" required autocomplete="email" aria-invalid="true" aria-describedby="email-err"> <span id="email-err" class="error" role="alert"> <svg aria-hidden="true">…</svg> Error: Enter a valid email </span> </div> <div> <label for="password">Password <span aria-hidden="true">*</span></label> <input id="password" name="password" type="password" required autocomplete="new-password" aria-describedby="password-hint"> <span id="password-hint" class="hint">At least 12 characters</span> </div> <button type="submit">Create account</button> </form> ``` ```css .error { color: #b00020; } /* ≥4.5:1 on white */ ``` **Notes:** - Only render the email error and `aria-invalid="true"` after validation fails. Set them dynamically rather than showing them on load. - If the error appears after submit, move focus to the first invalid field. - Verify focus outlines on the inputs and button are visible. Nothing here removes them, but check your CSS.
without
# Accessibility problems and fixes ## 1. Placeholders are used as labels (the biggest issue) Placeholders disappear on input, usually have poor contrast, and aren't reliable accessible names. This fails WCAG 1.3.1, 3.3.2, and 4.1.2. **Fix:** Add a visible `<label>` associated with each input. ## 2. "Required" is only conveyed by an asterisk in the placeholder Screen readers may not announce it, and the meaning of `*` isn't explained. **Fix:** Add `required` to the inputs, and put the asterisk in the label, hidden from AT if you like. Explain it at the top of the form ("* required"). ## 3. The error message is not programmatically tied to the field - It isn't linked to the input, so screen reader users won't hear it when focusing the field. - It isn't announced when it appears. - It's conveyed by color alone (WCAG 1.4.1), and pure `red` on white is about 4:1, which fails the 4.5:1 minimum. - It seems to be always visible, even before the user has made an error. **Fix:** - Add `aria-invalid="true"` to the input when it's invalid.
Show the rest (74 more lines)
- Link the message with `aria-describedby`. - Inject it with `role="alert"` or an `aria-live` region. - Use a darker red (e.g. `#c00` or `#b00020`) and add an icon or the word "Error" so color isn't the only cue. - Render it only when the field is invalid. ## 4. The hint isn't associated with the password input Screen reader users won't hear "At least 12 characters" when they reach the field. **Fix:** Give the hint an `id` and reference it from the input with `aria-describedby`. ## 5. The submit button is `disabled` Disabled buttons are skipped by keyboard focus, have no contrast requirements, and give no explanation of why they're disabled. Users can't discover what's wrong. **Fix:** Keep the button enabled. Validate on submit, show clear errors, and move focus to the first invalid field (or to an error summary). ## 6. Missing `autocomplete` and `name` attributes This fails WCAG 1.3.5 (Identify Input Purpose) and hurts password managers. **Fix:** Use `autocomplete="email"` and `autocomplete="new-password"`, and add `name` attributes. ## 7. Smaller items to check - Don't remove focus outlines, and make sure they have sufficient contrast (2.4.7, 1.4.11). - Don't block paste in the password field. - Consider a "Show password" toggle (a real `<button>` with `aria-pressed`). - Give the form an accessible name if it's not obvious from the page's heading context. - Move the inline `style` to CSS. --- ## Corrected example ```html <form aria-labelledby="signup-title" novalidate> <h2 id="signup-title">Create your account</h2> <p>Fields marked * are required.</p> <div class="field"> <label for="email">Email <span aria-hidden="true">*</span></label> <input id="email" name="email" type="email" autocomplete="email" required aria-required="true" aria-invalid="true" aria-describedby="email-error"> <span id="email-error" class="error" role="alert"> <svg aria-hidden="true" width="16" height="16"><!-- icon --></svg> Error: Enter a valid email address, like name@example.com </span> </div> <div class="field"> <label for="password">Password <span aria-hidden="true">*</span></label> <input id="password" name="password" type="password" autocomplete="new-password" required aria-required="true" minlength="12" aria-describedby="password-hint"> <span id="password-hint" class="hint">At least 12 characters</span> </div> <button type="submit">Create account</button> </form> ``` ```css .error { color: #b00020; } /* ≥ 4.5:1 on white */ input:focus-visible, button:focus-visible { outline: 3px solid #1a5fb4; outline-offset: 2px; } ``` In the example, the error span is shown with `aria-invalid="true"` just to demonstrate the invalid state. In production, add or remove both dynamically based on validation. On failed submit, focus the first invalid field. After these changes, test with keyboard only, a screen reader (NVDA/VoiceOver), 200% zoom, and a contrast checker.

Answers are shown exactly as the model wrote them, from the first try, and nothing is picked by hand.

The models

2 models

Claude Sonnet 5.5

made by Anthropic

claude-sonnet-5-5 · revision not reported by the provider

1 try per case · tested 3 Nov 2026

GPT-6.1 Sol

made by OpenAI

gpt-6.1-sol · revision not reported by the provider

1 try per case · tested 3 Nov 2026

The tasks

written by Carte Blanche
accordion-minimal-fix

“Fix the accessibility issue in this accordion. Please keep the change small, we can't refactor this component right now.”

contrast-and-focus-css

“Accessibility audit on our marketing site CSS, please.”

custom-modal-focus

“Here's our custom modal. Keyboard users say it's hard to use. What's wrong and how do I fix it?”

signup-form-errors

“Our signup form fails accessibility review. What needs fixing?”

toolbar-names-and-buttons

“Can you check this toolbar for accessibility problems?”

Install

Download the .zip and unzip it. Put its files, in a folder named fixing-accessibility, here:

Claude Code~/.claude/skills/Every project. For one project only, use .claude/skills/ inside it.
Codex~/.agents/skills/Every project. For one repository only, use .agents/skills/ inside it.
Cursor~/.cursor/skills/Cursor also reads the Claude Code and Codex folders above.

Then start a new session and ask for what the skill does. Other agents that read SKILL.md have their own skills folder; check their docs.

Coming after launch: get skills from inside your agent, no download needed.