Skip to content
ExamsLibraryFor agentsConnect your agent

Forms that don't nag

beginner·8 min·Markdown for agents

What this lesson teaches

Validate fields at the right moment, write errors that fix themselves, and wire the dozen small behaviors every form owes its user.

  1. 1The cadence
  2. 2What an error looks like
  3. 3When not to validate
  4. 4Everything else a form does

The field below opens mid-word, already scolding you for maya@. Switch to On blur, type maya@ again, then tap the name field.

Validate
Add the domain, like name@company.com
Tap here to leave the email field
Errors shown while you were still typing: 1Type maya@ and watch it scold you.
The same email field. One scolds you mid-word; the other waits until you are done.

The keystroke version shows an error after the first letter. You were not wrong; you were not finished. That is the cardinal sin of form validation: punishing someone for an action still in progress.

The cadence

means checking each field where it is, as the person moves through the form, instead of one list of errors after Submit. The timing is the whole craft:

  1. While typing: positive signals only

    No red borders, no error text. The only feedback allowed is good news: a small check when the field becomes valid.
  2. On blur: the real check

    When the person leaves the field (), validate. If it fails, show the error now. They said "I'm done"; tell them if it's wrong.
  3. Once an error shows: check on every change

    From then on, re-validate on each keystroke so the error clears the moment the value is fixed. Making them leave the field again to find out reads as the form still being cross.
  4. On submit: a final pass

    Catch what blur missed (rules across fields, server checks). If anything fails, move focus to the first invalid field.

Here it is in a real product: the error lands only when the cursor moves on to Password, and clears as the ending is typed.

app.northwind.co/signup
Create your account
Work email
We'll send a link to confirm it
Password
••••••••••••••
Create accountCreating accountAccount created
Error on blur, cleared while fixing, then the button confirms on itself.

What an error looks like

Each failing field gets three things, together: a danger-colored border, a message directly under the field (13 to 14px, in its own lane that exists before the error does, so the form never jumps), and aria-invalid plus aria-describedby on the input so a screen reader reads the message with the field.

Rules that span two fields, like "passwords match" or "start date before end date", are checked when the second field blurs. The first one cannot be wrong on its own.

✕ Before

Invalid input.
Please enter a valid email.

✓ After

Add the domain, like name@company.com

Good error copy says what is wrong and how to fix it, skips "Please", never says "Invalid", and sounds like a person.

When not to validate

  • A single search box: just search.
  • A comment or chat message: there is no wrong answer.
  • An inline rename: validate on Enter, not on blur, since the person may click away with the value intact.

And mark only the fields that are truly required; label the rest "Optional".

Everything else a form does

Validation is one job. A form owes about a dozen more, and every one is easy to forget.

BehaviorThe rule
LabelsEvery input has a real <label>, so clicking it focuses the field. A placeholder is not a label
Input typestype="email", tel, url, password: the right phone keyboard and autofill come free
Font sizeInputs at 16px or more on phones, or iOS zooms the page in
EnterWrap fields in a <form> so Enter submits; a textarea submits on Cmd or Ctrl+Enter
SubmittingDisable the button while the request runs and change its label ("Saving…"); re-enable it in finally, or one failure locks the form
FailureA failed submit says so and keeps every value. Never make anyone retype
Icons in the fieldPositioned over the input with padding reserved, never a sibling the click misses
AutofocusFirst field in a desktop dialog, never on touch, where it throws the keyboard up
PrefillAnything you already know: the current username, the ?email= from an invite link
AccidentsLong forms warn before leaving, or keep a draft, so a stray back-swipe costs nothing

fetch does not throw on a server error. An agent's try { await fetch() } catch {} treats a 500 as success. Check response.ok, and never call reset() before you know it worked.

Auto-advancing code boxes feel clever until someone needs to fix one digit.
Validation cadence· for your agent
Rework this form's validation:
- While typing: no errors; show a small check inside the field once it is valid.
- On blur: validate and show the error under the field in a reserved message lane (no layout shift).
- Once a field has shown an error, re-validate on every change so it clears as soon as it is fixed.
- On submit: run everything, focus the first invalid field. Cross-field rules run when the second field blurs.
- Error copy says what is expected ("Add the domain, like name@company.com"), no "Please", no "Invalid".
- Inputs have <label>, the right type, 16px font, aria-invalid and aria-describedby.
- The submit button disables with "Saving…" while in flight, re-enables in finally; a failed submit keeps every value and checks response.ok.

Quick check

When should an email field first show 'Add the domain, like name@company.com'?

What this lesson builds on and leads to, across every course.

  • Routing the job
  • Words matter
  • Briefs that work