forms — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited forms (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
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.
Forms must be understandable and operable with keyboards, touch, speech input, screen readers, screen magnification, browser autofill, password managers, and other assistive technologies. Requirements apply to initial entry, validation, recovery, review, submission, and authentication. Users must be able to complete time-limited forms when a time limit is not essential, and complete the form at 200%/400% zoom without losing content or functionality.
Prefer native HTML controls. Build a custom widget only when native controls cannot meet the documented requirement, and implement the applicable keyboard, focus, name, role, state, and value behaviour.
| 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 |
Do not assign severity from the violated rule alone — determine it from the actual form and task. Consider: whether the defect blocks submission or recovery; whether it affects authentication, payment, health, legal, safety, or essential public services; how many users/controls are affected; whether a reasonable workaround exists; whether it comes from a shared component; and whether data can be lost.
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
<label> is also validaria-label/aria-labelledby only when a visible HTML label is notpractical, and ensure the accessible name includes the words users can see (speech-input users identify controls by their visible label)
<button>Save</button>)don't need a separate label
<!-- 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.
Required/optional fields: explain how required fields are identified before the first field (e.g., "Fields marked 'required' must be completed"). Don't rely on an asterisk or colour alone. Don't mark every optional field when marking the smaller set of required fields would be clearer, or vice versa.
Errors must be programmatically associated with their field. Absent error identification is Critical — blind users receive no indication something is wrong.
aria-invalid="true" after validation determinesthey are invalid; remove the invalid state and error association after correction
aria-describedby (or atested aria-errormessage implementation); preserve any existing hint association
<label for="email">Email address</label>
<p id="email-hint">We will send the receipt to this address.</p>
<input id="email" name="email" type="email" autocomplete="email"
aria-invalid="true" aria-describedby="email-hint email-error">
<p id="email-error">
<strong>Error:</strong> Enter an email address in the format [email protected].
</p>Do not put role="alert" on error text that is already present when the page loads — a live region only announces changes made after it is established. Avoid combining several competing announcement mechanisms without AT testing.
Provide an error summary when several fields can fail, the form is long, errors may be outside the viewport, or users would otherwise have difficulty locating them. No error summary on multi-error, long, or complex forms is Critical.
<div id="error-summary" tabindex="-1" aria-labelledby="error-heading">
<h2 id="error-heading">There are 2 errors</h2>
<ul>
<li><a href="#email">Email address: enter a valid email address</a></li>
<li><a href="#dob-day">Date of birth: enter a real date</a></li>
</ul>
</div>failed submission and move focus to it
must move focus to the relevant control
count when errors change
role="alert" when focus movement already causes a clearannouncement, unless testing shows an additional live announcement helps
focus on every inline validation change
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.
Authentication must not require users to remember, manipulate, or transcribe information unless a conforming alternative or assistance mechanism exists. At minimum:
— do not split a one-time code into controls that prevent pasting the whole code
username, current-password, new-password,one-time-code)
or a cognitive puzzle; account recovery must meet the same requirements as login
provide an applicable alternative
<label for="username">Email address</label>
<input id="username" name="username" type="email" autocomplete="username">
<label for="password">Password</label>
<input id="password" name="password" type="password" autocomplete="current-password">
<button type="button" aria-controls="password" aria-pressed="false">
Show password
</button>A show-password button must update its visible label/pressed state consistently, preserve focus, and not clear the value.
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>Keep legends concise; don't use <fieldset> solely for visual styling. For a checkbox set that allows multiple selections, state that users may select more than one.
Choose type, autocomplete, and inputmode independently according to what the value means. This surfaces the correct virtual keyboard on mobile and provides semantic meaning to AT.
| Information | Recommended control |
|---|---|
| Email address | type="email" autocomplete="email" |
| Telephone number | type="tel" autocomplete="tel" |
| Website address | type="url" autocomplete="url" |
| Search query | type="search" |
| Current / new password | type="password" autocomplete="current-password" / "new-password" |
| One-time code | One text input, autocomplete="one-time-code" inputmode="numeric" |
| Numeric quantity with increment/decrement semantics | type="number" |
| Numeric-looking identifier, postal code, card number | type="text" with matching inputmode/autocomplete |
Use type="number" only for values on which mathematical operations make sense and min/max/step are useful — never for identifiers or values that may contain leading zeros:
<label for="quantity">Quantity</label>
<input id="quantity" name="quantity" type="number" min="1" max="20" step="1">
<label for="postal-code">Postal code</label>
<input id="postal-code" name="postal-code" type="text" autocomplete="postal-code">inputmode requests a virtual keyboard; it does not validate the value. Add pattern only when the resulting constraint and error message are correct for every accepted value — do not assume all users enter ASCII digits.
Use a native <input type="date"> when it meets the product's locale, browser, and AT requirements — test the actual implementation. For dates like date of birth, separate labelled text inputs are often easier to understand and validate than a native date picker or custom calendar widget:
<fieldset>
<legend>Date of birth</legend>
<p id="dob-hint">For example, 23 4 1980.</p>
<label for="dob-day">Day</label>
<input id="dob-day" name="dob-day" inputmode="numeric"
autocomplete="bday-day" aria-describedby="dob-hint">
<label for="dob-month">Month</label>
<input id="dob-month" name="dob-month" inputmode="numeric" autocomplete="bday-month">
<label for="dob-year">Year</label>
<input id="dob-year" name="dob-year" inputmode="numeric" autocomplete="bday-year">
</fieldset>Do not build a custom calendar dialog unless users need calendar-based selection; if one is required, implement and test the complete dialog and grid keyboard interaction (see the APG Date Picker pattern).
<select> — singleNative <select> is well-supported by all AT. Always include a blank/placeholder option as the first <option> when no default is appropriate.
<select multiple> has poor discoverability (Shift/Ctrl-click) and is keyboard-unfriendly. Prefer checkboxes inside a `<fieldset>` for a short, known list. <select multiple> may be appropriate for expert interfaces but must be usability-tested with the intended audience; if used, add visible instructions (Hold Ctrl (or Cmd on Mac) to select multiple options.) referenced via aria-describedby.
<datalist>Provides autocomplete suggestions for a text input; natively keyboard accessible but announcement quality varies — test with your AT matrix. 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 instead.
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. The label, name, type, and autocomplete token should describe the same purpose.
<input type="text" id="name" autocomplete="name">
<input type="email" id="email" autocomplete="email">
<input type="tel" id="phone" autocomplete="tel">
<textarea id="street" autocomplete="street-address"></textarea>
<input type="text" id="city" autocomplete="address-level2">
<input type="text" id="postal" autocomplete="postal-code">Test autofill without assuming autocomplete="off" will or should disable password managers and browser assistance. Full token list: WCAG 1.3.5 Input Purposes.
type (a <button> inside a form defaults to submit)
like "Go" or "Continue" when a more specific label is practical
incomplete — users must be able to discover validation errors and recover
trapping focus
aria-live="polite" / role="status" for routine progress andsuccessful completion; reserve assertive announcements for urgent information that cannot wait
heading there
state unless necessary at that moment
Silent async failures are Serious — screen reader users have no indication the action completed or failed.
For forms that create legal commitments, financial transactions, test responses, or changes to user-controlled data, provide at least one:
<h2>Review your order</h2>
<dl>
<dt>Plan</dt><dd>Annual plan</dd>
<dt>Amount</dt><dd>$120.00</dd>
</dl>
<a href="/cart">Edit order</a>
<button type="submit">Confirm and pay $120.00</button>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.
Do not clear valid information after a validation error. Within the same multi-step process, information previously entered or supplied must be auto-populated or available for selection unless re-entry is essential, required for security, or no longer valid. Examples: "Billing address is the same as shipping address" option; carry an account identifier into later steps; preserve a search query on the results page. Protect personal information while preserving it — 3.3.7 does not require storing information between separate sessions.
Provide concise instructions before complex input groups, associated via aria-describedby. This is Moderate — most users can infer requirements, but users with cognitive disabilities benefit from upfront guidance.
Avoid time limits unless essential (WCAG 2.2.1). For an inactivity timeout using warn-and-extend:
permit extension at least ten times
previous location after it closes
<dialog id="timeout-dialog" aria-labelledby="timeout-title">
<h2 id="timeout-title">Your session will expire in 2 minutes</h2>
<p>Your unsaved information will be lost.</p>
<button type="button" id="extend-session">Stay signed in</button>
<button type="button" id="sign-out">Sign out</button>
</dialog>Open a native <dialog> with showModal() where supported — aria-modal alone does not create modal behaviour, manage focus, make the rest of the page inert, or provide Escape handling. 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
placeholders, errors, focus indicators, and disabled states
validation overlays
changes, unless users were advised of that behaviour
custom widget per its documented pattern; confirm focus order is logical; trigger errors and follow every error-summary link; confirm modal warnings contain focus and return it; test copy/paste/undo/redo/password-manager entry
value, hint, and error are understandable; required/invalid states are announced; grouped controls include their shared question; status updates announced once and remain visible; visible labels included in accessible names
overrides; touch targets and on-screen keyboard; browser autofill
correct errors out of summary order; confirm valid values are preserved; test server-side errors, network errors, expired sessions, duplicate submissions
complete password and one-time code; test every MFA/recovery path; confirm previously entered info is preserved in later steps
Automated checks can catch missing names, invalid ARIA, contrast failures, and some state problems, but cannot determine whether instructions are understandable, errors are useful, or the complete process is usable. Include HTML validation, accessibility-tree/accessible-name assertions, axe-core, keyboard interaction tests for custom widgets, focus-placement tests, and paste tests for authentication fields. Test server-rendered errors as well as client-side validation.
<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="number" used only where math/stepper semantics apply<datalist> usage tested with AT; constrained inputs use APG Combobox patternaria-invalid set on invalid fields; removed when correctedaria-describedby<dialog>Standards 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.