forms — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited forms (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Canonical source:examples/FORMS_ACCESSIBILITY_BEST_PRACTICES.mdinmgifford/ACCESSIBILITY.mdThis skill is derived from that file. When in doubt, the example is authoritative.
Apply these rules when implementing or reviewing any form, input, or validation flow. Only load this skill if the project contains forms.
All users must be able to understand, complete, and submit forms with assistive technologies, keyboard-only input, and varying cognitive needs.
| Level | Meaning |
|---|---|
| Critical | Blocks task completion entirely for one or more disability groups |
| Serious | Significantly impairs access; workaround unreasonable to expect |
| Moderate | Creates friction; workaround exists and is not too burdensome |
| Minor | Best-practice gap; marginal impact on access |
Every form control must have a programmatically associated label. Missing labels are Critical — screen reader users cannot identify the field.
contrast requirements, and is not reliably announced by all screen readers
<!-- Bad: placeholder as only label — CRITICAL issue -->
<input type="email" placeholder="Email address">
<!-- Good: explicit label -->
<label for="email">
Email address <span class="visually-hidden">(required)</span>
</label>
<input type="email" id="email" autocomplete="email" required>On `aria-required`: Do not add aria-required="true" to native inputs that already have the HTML required attribute — it is redundant and adds noise to the accessibility tree. Use aria-required only on custom widgets (e.g., role="combobox", role="listbox") that cannot use the native attribute.
Errors must be programmatically associated with their field. Absent error identification is Critical — blind users receive no indication something is wrong.
aria-invalid="true"aria-describedby<label for="email">Email address</label>
<input type="email" id="email"
aria-describedby="email-error"
aria-invalid="true">
<span id="email-error" role="alert">
Enter a valid email address, for example: [email protected]
</span>Remove aria-invalid and the error message once the field is corrected.
For multi-error submissions, provide a focused error summary so keyboard and screen reader users are immediately oriented to what went wrong. No error summary on multi-error forms is Critical.
<div role="alert" aria-labelledby="error-heading"
tabindex="-1" id="error-summary">
<h2 id="error-heading">There are 2 errors in this form</h2>
<ul>
<li><a href="#email">Email address – enter a valid email</a></li>
<li><a href="#dob">Date of birth – enter a real date</a></li>
</ul>
</div>Move focus to #error-summary immediately after a failed submit. Each link in the list must take focus directly to the affected field.
Never block paste, copy, or standard keyboard actions on any input. Blocking paste is Critical — users with motor disabilities and those who use password managers cannot re-type credentials they cannot physically type.
Use <fieldset> + <legend> for related controls. Missing grouping is Serious — radio and checkbox groups become unintelligible without a group label.
<fieldset>
<legend>Notification preferences</legend>
<label><input type="checkbox" name="email-notify"> Email</label>
<label><input type="checkbox" name="sms-notify"> SMS</label>
</fieldset>Use the most specific applicable input type. This surfaces the correct virtual keyboard on mobile and provides semantic meaning to AT.
| Need | Use | Avoid |
|---|---|---|
| Email address | type="email" | type="text" |
| Phone number | type="tel" | type="number" |
| Search | type="search" | type="text" |
| Web URL | type="url" | type="text" |
| Password | type="password" | type="text" |
| Date | See date guidance below | type="text" alone |
type="number" — use with cautiontype="number" has inconsistent screen reader support and unexpected behaviour (scroll wheel changes values, non-numeric characters silently stripped). *Prefer `type="text"` with `inputmode="numeric"` and `pattern="[0-9]"`** for quantities, codes, and amounts:
<!-- Good: text input with numeric keyboard on mobile -->
<label for="quantity">Quantity</label>
<input type="text" id="quantity"
inputmode="numeric"
pattern="[0-9]*"
autocomplete="off">
<!-- Only use type="number" when the stepper arrows are genuinely useful
and the value range is small and well-defined -->Native <input type="date"> has reasonable accessibility support in modern browsers but inconsistent screen reader announcement. Evaluate against your AT support matrix before using.
For complex date ranges or date pickers, prefer three separate labelled inputs (day/month/year) over a custom calendar widget — they work with all AT without JavaScript:
<fieldset>
<legend>Date of birth</legend>
<label for="dob-day">Day</label>
<input type="text" id="dob-day" inputmode="numeric"
pattern="[0-9]{1,2}" autocomplete="bday-day"
aria-describedby="dob-hint">
<label for="dob-month">Month</label>
<input type="text" id="dob-month" inputmode="numeric"
pattern="[0-9]{1,2}" autocomplete="bday-month">
<label for="dob-year">Year</label>
<input type="text" id="dob-year" inputmode="numeric"
pattern="[0-9]{4}" autocomplete="bday-year">
<p id="dob-hint">For example: 23 4 1980</p>
</fieldset><select> — singleNative <select> is well-supported by all AT. Use it for choosing one option from a known, bounded list.
<label for="country">Country</label>
<select id="country" autocomplete="country-name">
<option value="">Select a country</option>
<option value="ca">Canada</option>
<option value="us">United States</option>
</select>Always include a blank/placeholder option as the first <option> when no default is appropriate — do not use selected to pre-select a value unless it genuinely reflects the user's likely choice.
<select multiple><select multiple> has poor usability for many users, including those with motor and cognitive disabilities. The multi-select interaction (hold Shift or Ctrl while clicking) is non-obvious and keyboard-unfriendly.
Prefer checkboxes inside a `<fieldset>` for multiple selection unless the list is very long and a search/filter is provided:
<!-- Prefer this over <select multiple> -->
<fieldset>
<legend>Topics (select all that apply)</legend>
<label><input type="checkbox" name="topic" value="html"> HTML</label>
<label><input type="checkbox" name="topic" value="css"> CSS</label>
<label><input type="checkbox" name="topic" value="js"> JavaScript</label>
</fieldset>If <select multiple> must be used, add visible instructions: <p id="select-hint">Hold Ctrl (or Cmd on Mac) to select multiple options.</p> and reference it with aria-describedby.
<datalist><datalist> provides autocomplete suggestions for a text input. It is natively keyboard accessible and announced by most screen readers, but announcement quality varies — test with your AT matrix.
<label for="browser">Preferred browser</label>
<input type="text" id="browser"
list="browsers"
autocomplete="off"
aria-describedby="browser-hint">
<datalist id="browsers">
<option value="Chrome">
<option value="Firefox">
<option value="Safari">
<option value="Edge">
</datalist>
<p id="browser-hint" class="visually-hidden">
Start typing to see suggestions, or type your own value.
</p>Note: <datalist> is not a replacement for a fully accessible combobox when the suggestion list is the only valid set of values. Use role="combobox" with the APG Combobox pattern when the input must be constrained to the list.
Missing autocomplete on personal-data fields is Serious — it directly impacts users with motor disabilities and cognitive conditions who rely on autofill to avoid retyping.
<input type="text" id="name" autocomplete="name">
<input type="email" id="email" autocomplete="email">
<input type="tel" id="phone" autocomplete="tel">
<input type="text" id="street" autocomplete="street-address">
<input type="text" id="city" autocomplete="address-level2">
<input type="text" id="postal" autocomplete="postal-code">
<input type="password" id="current-pass" autocomplete="current-password">
<input type="password" id="new-pass" autocomplete="new-password">Full list of valid autocomplete token values: WCAG 1.3.5 Input Purposes
aria-live="polite" for non-critical updates (character counts, inline validation)aria-live="assertive" only for blocking failures — it interruptsthe screen reader immediately
Silent async failures are Serious — screen reader users have no indication the action completed or failed.
For forms that result in legal commitments, financial transactions, or permanent data changes, users must be able to do at least one of:
<!-- Confirmation step pattern -->
<h2>Review your order</h2>
<dl>
<dt>Item</dt><dd>Annual plan</dd>
<dt>Amount</dt><dd>$120.00</dd>
</dl>
<button type="submit">Confirm and pay</button>
<a href="/cart">Edit order</a>Absent error prevention on financial/legal forms is Serious. It may become Critical in regulated contexts (financial services, legal agreements, medical data) where the error cannot be undone.
Provide concise instructions before complex input groups. This is Moderate — most users can infer requirements, but users with cognitive disabilities benefit from upfront guidance.
Date: DD/MM/YYYYaria-describedbyIf the form session can expire, users must be warned before timeout occurs (WCAG 2.2.1 Timing Adjustable). Give at least 20 seconds to extend the session, and the warning itself must be accessible to AT.
<!-- Timeout warning — inject when ~2 minutes remain -->
<div role="alertdialog" aria-modal="true"
aria-labelledby="timeout-title" tabindex="-1" id="timeout-dialog">
<h2 id="timeout-title">Your session will expire in 2 minutes</h2>
<p>You will lose any unsaved form data.</p>
<button type="button" id="extend-session">Stay signed in</button>
</div>Move focus to the warning dialog. On "Stay signed in", reset the timer and return focus to where the user was.
Session timeout is Moderate for informational forms; Critical for transactional or legal submissions where data loss cannot be recovered.
the field just left — do not validate fields the user has not yet visited
<label>required used on native inputs; aria-required not added redundantly<fieldset>/<legend> used for grouped controlsautocomplete values on personal-data fields<select multiple> replaced with checkboxes where feasibletype="text" inputmode="numeric" used in preference to type="number"for quantities/codes (unless stepper is genuinely needed)
<datalist> usage tested with AT; constrained inputs use APG Combobox patternaria-invalid set on invalid fields; removed when correctedaria-describedbyStandards horizon: These rules target WCAG 2.2 AA. WCAG 3.0 is in development; its proposed APCA contrast model may affect error-state colour choices but core labelling, grouping, and error requirements are expected to carry forward. Monitor: <https://www.w3.org/TR/wcag-3.0/>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.