# Forms that don't nag

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

Module: Components and states · Level: beginner · 8 min · https://school.agents.org.in/design/components/forms

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

*[Interactive demo: 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

Inline validation (Checking each form field where it is, as the person moves through the form, instead of listing every error at the top after they press Submit) 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:

  **Step: 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.

  **Step: On blur: the real check.** When the person leaves the field (blur (The moment focus leaves a form field, by tabbing or clicking elsewhere. It is the right time to check the field, because the person has signalled they are done with it)), validate. If it fails, show the error now. They said "I'm done"; tell them if it's wrong.

  **Step: 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.

  **Step: 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.

*[Interactive demo: 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 (avoid):**

Invalid input.<br />Please enter a valid email.

  **After (do this):**

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.

| Behavior | The rule |
|---|---|
| Labels | Every input has a real `<label>`, so clicking it focuses the field. A placeholder is not a label |
| Input types | `type="email"`, `tel`, `url`, `password`: the right phone keyboard and autofill come free |
| Font size | Inputs at 16px or more on phones, or iOS zooms the page in |
| Enter | Wrap fields in a `<form>` so Enter submits; a textarea submits on Cmd or Ctrl+Enter |
| Submitting | Disable the button while the request runs and change its label ("Saving…"); re-enable it in `finally`, or one failure locks the form |
| Failure | A failed submit says so and **keeps every value**. Never make anyone retype |
| Icons in the field | Positioned over the input with padding reserved, never a sibling the click misses |
| Autofocus | First field in a desktop dialog, never on touch, where it throws the keyboard up |
| Prefill | Anything you already know: the current username, the `?email=` from an invite link |
| Accidents | Long 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.

> **Autocomplete, curated.** Keep `autocomplete` on for names, addresses and payment. Turn it and spellcheck off for usernames, codes and search. Use `inputmode="numeric"` and `autocomplete="one-time-code"` on verification codes, and `current-password` vs `new-password` so password managers do the right thing.

*(Auto-advancing code boxes feel clever until someone needs to fix one digit.)*

**Validation cadence (paste into your agent):**

```text
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.
```

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

- [ ] As soon as the first character is typed
- [x] When the person leaves the field with an invalid value
- [ ] Only after Submit, in a list at the top
- [ ] Never; disable the Submit button instead

*Why:* Hard validation happens on blur, when the person has signalled they are done with the field. After that, it re-checks on every change so the message clears when fixed.

## In the skill

- https://school.agents.org.in/skill/components/forms-validation.md
- https://school.agents.org.in/skill/components/forms-behavior.md
