Form validation & error recovery

Building a form that takes input is the easy half. The hard half is what happens when the input is wrong — or when the submission is one a person can’t safely take back. Error recovery is where accessibility, usability, and legal risk all meet: a rejected field that never says which field, a “please fix the errors” message that never says how, or a one-click purchase with no way to review before it commits. Each of these turns an ordinary mistake into a dead end, and the people it strands first are those using screen readers, magnification, or working under cognitive load.

This lesson works through three defects in the error-recovery path, all drawn from WCAG’s Input Assistance guideline. They build on one another: identify the error in text, then suggest how to fix it, then — for consequential actions — give the user a chance to review, confirm, or undo before the form commits.

What you’ll learn

How to identify a field in error in text and tie that text to the field (3.3.1); how to tell the user how to correct the value when a fix is known, without leaking anything that would weaken security (3.3.3); and how to give a review, confirmation, or undo step before a legal, financial, or data-changing submission becomes final (3.3.4).

Standards this lesson maps to
Standard Criterion Level What it requires
WCAG 2.2 3.3.1 Error Identification A If an input error is detected automatically, the item in error is identified and described to the user in text.
WCAG 2.2 3.3.3 Error Suggestion AA If an error is detected and a correction is known, the suggestion is provided, unless it would jeopardise security or the purpose of the content.
WCAG 2.2 3.3.4 Error Prevention (Legal, Financial, Data) AA For legal commitments, financial transactions, or data changes, submissions are reversible, checked for errors, or confirmed before they finalise.
WCAG 2.2 4.1.3 Status Messages AA Error and status messages are exposed to assistive technology without moving focus.
EN 301 549 9.3.3 (incorporates WCAG) European harmonised standard; references the WCAG A/AA set including the Input Assistance criteria.
Section 508 502.3 / 504 (incorporates WCAG A & AA) US federal ICT must meet WCAG 2.0 Level A and AA, including error identification, suggestion, and prevention.
ADA Title II WCAG 2.1 AA (DOJ rule) AA US state/local government web content must conform to WCAG 2.1 AA, which includes 3.3.1, 3.3.3, and 3.3.4.

The three problems we’ll fix

Each card below isolates one common error-recovery defect. For every issue you get a plain-language statement of the problem, a Bad example (shown as escaped, non-running code so it can’t affect this page), a Good example, the copyable Code, and an ordered fix.

Error not identified in text or associated with its field

WCAG 2.2 · 3.3.1 A 4.1.3 AA EN 301 549 Section 508

When validation fails, the user has to learn two things: which field is wrong and what is wrong with it — both in text. A red border, a coloured outline, or a small icon conveys neither to a screen reader, and colour alone also fails low-vision and colour-blind users. The error text must exist as words, be tied to the field with aria-describedby, and the field must carry aria-invalid="true" so its invalid state is exposed. If the error appears after submit without focus moving, it also needs to be announced (4.1.3).

Bad

The only signal that something is wrong is a red border applied with a class. There is no text, no association, and nothing exposed to assistive technology.

bad-error-identify.html
<label for="zip">ZIP code</label>
<input type="text" id="zip" name="zip" class="is-invalid">
<!-- .is-invalid only paints a red border; no text, no link to the field -->

Good

The error is real text, linked to the field with aria-describedby, and the field is marked aria-invalid="true". The message names the problem, not just the fact of failure.

good-error-identify.html
<label for="zip">ZIP code</label>
<input type="text" id="zip" name="zip"
       aria-describedby="zip-error" aria-invalid="true">
<p class="error" id="zip-error">
  <span class="error__icon" aria-hidden="true">⚠</span>
  ZIP code is required.
</p>

Code

On submit, gather every failing field into a summary, give it role="alert", and move focus to it so the failure is announced and each entry links to the field it names (3.3.1 + 4.1.3).

error-summary.html
<div role="alert" tabindex="-1" id="errors">
  <h2>There are 2 problems</h2>
  <ul>
    <li><a href="#zip">ZIP code is required.</a></li>
    <li><a href="#email">Enter a valid email address.</a></li>
  </ul>
</div>
<!-- After render: document.getElementById('errors').focus(); -->

How to fix

  1. Describe every detected error in text that names the field and the problem; never rely on colour, border, or icon alone.
  2. Link the message to its field with aria-describedby and set aria-invalid="true" while the field is in error.
  3. On submit, build an error summary, link each item to its field, and move focus to the summary so the failure is announced (4.1.3).
  4. Remove aria-invalid and the message once the value becomes valid, so stale errors aren’t announced.

No suggestion for how to fix the error

WCAG 2.2 · 3.3.3 AA EN 301 549 ADA Title II

“Invalid input” tells the user they failed but not how to succeed. When you can detect why a value was rejected, Error Suggestion asks you to say what a correct value looks like: the required format, the allowed range, the missing piece, or a valid example. This matters most for people with cognitive disabilities, who may not infer the rule from a generic rejection. The one exception is security: if naming the fix would weaken it — for example, confirming which half of a username/password pair was wrong — you may withhold the suggestion.

Bad

The message confirms failure and stops there. The user is told the date is invalid but not what format the field expects.

bad-no-suggestion.html
<label for="dob">Date of birth</label>
<input type="text" id="dob" name="dob"
       aria-describedby="dob-error" aria-invalid="true">
<p class="error" id="dob-error">Invalid date.</p>

Good

The message states the expected format and gives a concrete example, so the user can correct the value without guessing. The instruction is also shown up front, before any error occurs.

good-suggestion.html
<label for="dob">Date of birth</label>
<input type="text" id="dob" name="dob"
       aria-describedby="dob-hint dob-error" aria-invalid="true">
<p class="hint" id="dob-hint">Use the format DD/MM/YYYY.</p>
<p class="error" id="dob-error">
  Enter your date of birth as DD/MM/YYYY, for example 09/04/1990.
</p>

Code

Tailor the suggestion to the actual failure, and recognise the security exception: on a sign-in form, keep the message generic so it doesn’t reveal which credential was wrong.

suggestion-by-reason.html
<!-- Specific, because the rule is safe to share -->
<p class="error">Password must be at least 8 characters. You entered 5.</p>

<!-- Range suggestion -->
<p class="error">Enter a quantity between 1 and 20.</p>

<!-- Security exception: stay generic on sign-in -->
<p class="error">That username and password don’t match. Try again.</p>

How to fix

  1. When you know why a value failed, say what a correct value looks like — a format, a range, a missing element, or a valid example.
  2. Show format instructions up front as a hint as well, so users avoid the error in the first place.
  3. Match the suggestion to the actual failure rather than reusing one generic message for every case.
  4. Withhold the suggestion only where revealing it would jeopardise security (such as which sign-in credential was wrong).

Legal or financial submit with no review, confirm, or undo

WCAG 2.2 · 3.3.4 AA EN 301 549 Section 508 ADA Title II

Some submissions can’t simply be tried again — placing an order, transferring money, accepting terms, deleting an account, or casting a vote. Error Prevention says that for legal commitments, financial transactions, or changes to stored data, at least one safety net must be present: the action is reversible, the data is checked for errors with a chance to correct, or the user confirms before it finalises. A slip from a screen reader user, a mis-tap on a touch target, or a double submit shouldn’t cost real money. Meeting any one of the three options satisfies the criterion.

Bad

A single button commits the payment immediately. There is no review screen, no confirmation, and no way to undo — one click is final.

bad-instant-submit.html
<form action="/charge" method="post">
  <!-- amount and card fields … -->
  <button type="submit">Pay £499 now</button>
</form>
<!-- Submits and charges in one step: no review, confirm, or undo -->

Good

A review step shows what will happen and asks for explicit confirmation before the charge is made. The user can go back and edit, and the confirm control is separate from the form’s normal submit.

good-review-confirm.html
<h1>Review your order</h1>
<dl>
  <dt>Item</dt><dd>Annual plan</dd>
  <dt>Total</dt><dd>£499.00</dd>
  <dt>Card</dt><dd>Visa ending 4242</dd>
</dl>
<a href="/checkout">Edit details</a>
<form action="/charge" method="post">
  <label><input type="checkbox" name="confirm" required>
    I confirm this £499 charge</label>
  <button type="submit">Confirm and pay</button>
</form>

Code

Any one of the three mechanisms is enough. Reversible covers many cases: provide an undo window, or a clear cancellation path, instead of a confirm step.

reversible-undo.html
<!-- Option 1: reversible — an undo window after the action -->
<div role="status">
  Account deleted.
  <a href="/undo-delete?token=…">Undo (available for 14 days)</a>
</div>

<!-- Option 2: checked — re-validate and show a correction step -->
<!-- Option 3: confirmed — explicit confirm before finalising -->

How to fix

  1. Identify submissions that are legal commitments, financial transactions, or changes to or deletions of the user’s stored data.
  2. Provide at least one safety net: make the action reversible, check the data with a chance to correct, or require explicit confirmation before it commits.
  3. For a confirm step, show a review of exactly what will happen and a clear way to go back and edit.
  4. For a reversible action, offer a real undo or cancellation window and tell the user how long they have.
  5. Make the safety net itself accessible — keyboard reachable, announced, and not time-pressured beyond what 2.2.1 allows.

Recap

  • When a field is rejected, name it and describe the problem in text — not by colour or position alone — and tie that text to the field with aria-describedby and aria-invalid (3.3.1).
  • When you know how to fix the value, say so: a format, an allowed range, or a valid example. Hold the suggestion back only where revealing it would weaken security (3.3.3).
  • For legal, financial, or data-changing actions, make the submission reversible, add a checking step, or require explicit confirmation before it commits (3.3.4).

Identify, suggest, then prevent — the same three steps satisfy WCAG, EN 301 549, Section 508, and ADA Title II together.