$npx -y skills add mgifford/accessibility-skills --skill formsLoad this skill whenever the project contains forms, inputs, selects, checkboxes, radio buttons, text areas, or any validation flow. Under no circumstances create a form without visible labels, error identification, and keyboard accessibility. Absolutely always associate every in
| 1 | # Forms Accessibility Skill |
| 2 | |
| 3 | > **Canonical source**: `examples/FORMS_ACCESSIBILITY_BEST_PRACTICES.md` in `mgifford/ACCESSIBILITY.md` |
| 4 | > This skill is derived from that file. When in doubt, the example is authoritative. |
| 5 | |
| 6 | Apply these rules when implementing or reviewing any form, input, or validation flow. |
| 7 | **Only load this skill if the project contains forms.** |
| 8 | |
| 9 | --- |
| 10 | |
| 11 | ## Core Mandate |
| 12 | |
| 13 | Forms must be understandable and operable with keyboards, touch, speech input, |
| 14 | screen readers, screen magnification, browser autofill, password managers, and |
| 15 | other assistive technologies. Requirements apply to initial entry, validation, |
| 16 | recovery, review, submission, and authentication. Users must be able to |
| 17 | complete time-limited forms when a time limit is not essential, and complete |
| 18 | the form at 200%/400% zoom without losing content or functionality. |
| 19 | |
| 20 | Prefer native HTML controls. Build a custom widget only when native controls |
| 21 | cannot meet the documented requirement, and implement the applicable keyboard, |
| 22 | focus, name, role, state, and value behaviour. |
| 23 | |
| 24 | --- |
| 25 | |
| 26 | ## Severity Scale (this skill) |
| 27 | |
| 28 | | Level | Meaning | |
| 29 | | --- | --- | |
| 30 | | **Critical** | Blocks task completion entirely for one or more disability groups | |
| 31 | | **Serious** | Significantly impairs access; workaround unreasonable to expect | |
| 32 | | **Moderate** | Creates friction; workaround exists and is not too burdensome | |
| 33 | | **Minor** | Best-practice gap; marginal impact on access | |
| 34 | |
| 35 | Do not assign severity from the violated rule alone — determine it from the |
| 36 | actual form and task. Consider: whether the defect blocks submission or |
| 37 | recovery; whether it affects authentication, payment, health, legal, safety, |
| 38 | or essential public services; how many users/controls are affected; whether a |
| 39 | reasonable workaround exists; whether it comes from a shared component; and |
| 40 | whether data can be lost. |
| 41 | |
| 42 | --- |
| 43 | |
| 44 | ## Critical: Labels |
| 45 | |
| 46 | Every form control must have a programmatically associated label. |
| 47 | **Missing labels are Critical** — screen reader users cannot identify the field. |
| 48 | |
| 49 | * Never use placeholder text as the sole label — it disappears on input, fails |
| 50 | contrast requirements, and is not reliably announced by all screen readers |
| 51 | * Required fields must be identified in text, not by colour alone |
| 52 | * A control nested inside its own `<label>` is also valid |
| 53 | * Use `aria-label`/`aria-labelledby` only when a visible HTML label is not |
| 54 | practical, and ensure the accessible name includes the words users can see |
| 55 | (speech-input users identify controls by their visible label) |
| 56 | * Controls whose content already supplies a name (e.g., `<button>Save</button>`) |
| 57 | don't need a separate label |
| 58 | |
| 59 | ```html |
| 60 | <!-- Bad: placeholder as only label — CRITICAL issue --> |
| 61 | <input type="email" placeholder="Email address"> |
| 62 | |
| 63 | <!-- Good: explicit label --> |
| 64 | <label for="email"> |
| 65 | Email address <span class="visually-hidden">(required)</span> |
| 66 | </label> |
| 67 | <input type="email" id="email" autocomplete="email" required> |
| 68 | ``` |
| 69 | |
| 70 | **On `aria-required`:** Do not add `aria-required="true"` to native inputs |
| 71 | that already have the HTML `required` attribute — it is redundant and adds |
| 72 | noise to the accessibility tree. Use `aria-required` only on custom widgets |
| 73 | (e.g., `role="combobox"`, `role="listbox"`) that cannot use the native attribute. |
| 74 | |
| 75 | **Required/optional fields:** explain how required fields are identified |
| 76 | before the first field (e.g., "Fields marked 'required' must be completed"). |
| 77 | Don't rely on an asterisk or colour alone. Don't mark every optional field |
| 78 | when marking the smaller set of required fields would be clearer, or vice versa. |
| 79 | |
| 80 | --- |
| 81 | |
| 82 | ## Critical: Error Identification |
| 83 | |
| 84 | Errors must be programmatically associated with their field. |
| 85 | **Absent error identification is Critical** — blind users receive no indication |
| 86 | something is wrong. |
| 87 | |
| 88 | * Mark invalid fields with `aria-invalid="true"` after validation determines |
| 89 | they are invalid; remove the invalid state and error association after correction |
| 90 | * Error messages must be specific and actionable — not just "invalid input" |
| 91 | * Never use colour or an icon alone to indicate an error |
| 92 | * Associate the error message with the field via `aria-describedby` (or a |
| 93 | tested `aria-errormessage` implementation); preserve any existing hint association |
| 94 | * Keep the entered value unless security requires otherwise |
| 95 | |
| 96 | ```html |
| 97 | <label for="email">Email address</label> |
| 98 | <p id="email-hint">We will send the receipt to this address.</p> |
| 99 | <input id="email" name="email" type="email" autocomplete="email" |
| 100 | aria-invalid="true" aria-describedby="email-hint email-error"> |
| 101 | <p id="email-error"> |
| 102 | <strong>Error:</strong> Enter an email address in the format name@example.com. |
| 103 | </p> |
| 104 | ``` |
| 105 | |
| 106 | D |