---
name: html-forms
description: "Audits and fixes HTML forms so labels, input types, autofill and validation work for every user, without renaming the field names a server depends on. Runs a read-only standard-library checker that flags missing labels, placeholder-only fields, invented or missing autocomplete tokens, wrong email, phone and number types, radio groups without fieldset, autocomplete off on sign-in fields and buttons without a type, then builds a field table and applies fixed rules for labels, hints, autocomplete, validation timing, error messages and error summaries, with server-side validation required. Use whenever someone asks to fix or review a sign-in, sign-up, checkout, address or contact form, make autofill or password managers work, choose input types, write form error messages or decide when to validate. Do NOT use for landmarks, headings or links (use semantic-html), conversion copy or cutting fields (use form-optimisation), custom date pickers, authentication security or legal consent wording."
license: CC-BY-4.0
metadata:
  author: Afeez Adebayo
  author_url: https://afeezadebayo.com
  author_linkedin: https://www.linkedin.com/in/afeez-adebayo/
  author_x: https://x.com/za_icon
  source: https://afeezadebayo.com/skills-md/frontend-development/html-semantics/html-forms/
  category: frontend-development
  subcategory: html-semantics
  version: "1.0.0"
  created: "2026-10-23"
  updated: "2026-10-23"
---

# HTML Forms: Labels, Autofill and Validation That Work for Everyone

Forms are where websites lose people. A field with a placeholder and no label, a phone number typed into a `type="number"` box, an error message that only turns a border red: each one costs someone a sign-up or an order. The WebAIM Million 2026 report found missing form input labels on 51% of one million home pages.

This skill makes an agent audit and fix HTML forms with fixed rules for labels, input types, autofill tokens and validation. It starts with a read-only checker, writes a field-by-field plan, and changes markup in small approved batches without renaming the fields the server depends on. Validation always happens on the server too.

It works on plain HTML and on the rendered output of any framework or form library. Library and browser facts sit in the appendix.

## When to use this skill, and when not to

Use it when the user wants to:

- Audit or fix a sign-up, sign-in, checkout, address, contact or search form.
- Make a form work with browser autofill and password managers.
- Decide when to validate, how to show errors, and what the messages should say.
- Choose the right `type`, `inputmode` and `autocomplete` for each field.
- Review a pull request or generated form code.

Do not use it for these jobs:

| Request | Use instead |
|---|---|
| Landmarks, headings, buttons and links outside forms | `semantic-html.md` |
| Cutting fields, layout and copy to raise conversion | `form-optimisation.md` |
| Custom widgets with no native element (comboboxes, date pickers) | A component accessibility skill. This skill uses native controls |
| Security of authentication flows, password policy, CAPTCHA choice | A security review. This skill covers markup and validation only |

## Guardrails that apply to every run

1. **Propose, then change.** The agent reports and plans first. It edits files only after the user approves a batch, on a branch under version control. It never pushes, merges or deploys.
2. **Never rename `name` attributes on its own.** The server, analytics and saved autofill data read them. A rename needs the user's yes and a matching server change, listed in the plan.
3. **Never submit real forms.** Testing happens in a local or test environment with invented test data. The agent does not submit forms on live sites, make payments or create real accounts.
4. **Server-side validation is required.** Client-side checks help users and are easy to bypass. If the server does not validate a field, the report says so as a blocker.
5. **Personal data and credentials.** Forms collect names, emails, phone numbers, addresses, dates of birth, card numbers and passwords. The agent never asks for real values, never logs or pastes entered values, and uses obviously fake test data such as `test@example.com`. It never stores or repeats a password, one-time code or card number, even a test one, in the report.
6. **Batches stay small.** At most 2 forms or 30 field changes per batch (defaults), each shown as a diff.
7. **Nothing is invented.** Thresholds here are labelled defaults. Tool facts carry a source and the date read. Version-dependent facts carry **verify before relying on this**.
8. **Not legal advice.** Consent checkboxes, privacy notices, age checks and accessibility law are legal questions. The agent marks those fields up correctly and routes the wording and the decision to a person.
9. **Scope boundary.** The agent refuses to disable autofill on sign-in fields, to remove server-side checks, to add hidden tracking fields, or to change what data a form collects without the user's decision.

## Inputs the agent must have before starting

Ask for what is missing in one message and wait. Do not proceed on assumptions.

| # | Input | What to ask if missing |
|---|---|---|
| 1 | Target | "Which forms should I check? File paths or the URL of a test environment." |
| 2 | Purpose | "What is each form for: sign-in, sign-up, checkout, address, contact, search?" |
| 3 | Stack | "Plain HTML, or a framework or form library (React Hook Form, Formik, a server template)? Is there a schema library such as Zod or Yup?" |
| 4 | Server checks | "Does the server validate every field, and can you point me to that code?" |
| 5 | Error pattern | "Does your design system already have error message and error summary components?" |
| 6 | Countries | "Which countries do you take addresses and phone numbers from?" |
| 7 | Dependants | "Do analytics, tests or the server read field names or IDs?" |
| 8 | Test setup | "Is there a test environment where I can submit with fake data, and can you run Python 3 locally?" |

If input 4 is unknown, the agent flags "server validation unconfirmed" at the top of the report and continues with markup only.

## Workflow

### Step 0: Confirm scope and safety

1. Confirm inputs 1 to 8 and the branch name.
2. Tell the user the plan: checker, field table, decisions, batches, verification, report.
3. Gate 1: wait for a yes.

### Step 1: Run the checker

1. Save `scripts/forms_check.py` from this skill folder. It uses only the Python standard library, reads files and writes nothing.
2. Run it on rendered HTML where possible: `python3 scripts/forms_check.py form.html`.
3. Keep the output as the baseline.

| Code | Level | Meaning |
|---|---|---|
| `no-label` | error | No wrapping `label`, `label for`, `aria-label` or `aria-labelledby`. Notes when only a placeholder exists |
| `bad-autocomplete` | error | The `autocomplete` value is not a recognised token |
| `group-without-fieldset` | error | Two or more radios or checkboxes with one `name` sit outside a `fieldset` |
| `fieldset-without-legend` | error | A `fieldset` has no `legend` |
| `missing-autocomplete` | warn | The field's name or id suggests personal data and it has no `autocomplete` |
| `type-mismatch` | warn | An email or phone field has the wrong `type` |
| `number-for-identifier` | warn | `type="number"` on a phone, postcode or card field |
| `autocomplete-off-on-login` | warn | `autocomplete="off"` on a password, username or email field |
| `no-name` | warn | The control has no `name`, so it will not submit or autofill |
| `button-without-type` | warn | A form has more than one button and some have no `type` |
| `form-without-submit` | warn | No submit button found |

The checker reads names and IDs to guess a field's purpose. Its guesses are prompts for the field table, not decisions.

### Step 2: Build the field table

Write one row per field, using the template in the Templates section: purpose, visible label, hint, `type`, `inputmode`, `autocomplete`, required or optional, client rule, server rule and error messages. Every later decision fills a cell in this table. A field with an empty server rule is a blocker under guardrail 4.

### Step 3: Labels, hints and grouping

1. Every control has a visible label that stays on screen while typing. A placeholder is not a label: it disappears as soon as the user types.
2. Link the label with `for` and `id`, or wrap the control in the `label`.
3. Put format help in a hint below the label, linked with `aria-describedby`. Do not hide the hint in a placeholder.
4. Group related controls in `fieldset` with a `legend`: radio sets, checkbox sets, date parts, and address blocks that repeat (billing and shipping).
5. Mark required or optional fields consistently. If most fields are required, mark the optional ones "(optional)". State the convention once at the top of the form. Do not rely on colour or an asterisk alone.
6. Ask only for data the transaction needs. The W3C forms tutorial notes that extra fields raise abandonment. If a field looks unneeded, list it for the user to decide. The agent does not remove it.

### Step 4: Types, keyboards and autofill

Use this table. Browsers save and offer values by `name`, and some also use `id`, so keep both meaningful.

| Purpose | `type` | `autocomplete` | Notes |
|---|---|---|---|
| Full name | `text` | `name` | Use `given-name` and `family-name` only if the server needs them split |
| Email | `email` | `email` | Add `spellcheck="false"` |
| Phone | `tel` | `tel` | Keep it in one input. Do not use `type="number"` |
| Username on sign-in | `text` or `email` | `username` | Use `username` even when the username is an email |
| Password on sign-in | `password` | `current-password` | Never `off` |
| Password on sign-up or reset | `password` | `new-password` | State the rules before submission and link them with `aria-describedby` |
| One-time code | `text` | `one-time-code` | Add `inputmode="numeric"` |
| Address line | `text` | `address-line1`, `address-line2` | Or `street-address` in a single `textarea` |
| Town or city | `text` | `address-level2` | |
| Postcode | `text` | `postal-code` | Accept spaces and lower case. Not `type="number"` |
| Country | `select` | `country` or `country-name` | |
| Card number | `text` | `cc-number` | `inputmode="numeric"`, one input |
| Card expiry | `text` or `select` | `cc-exp`, or `cc-exp-month` and `cc-exp-year` | Native `select` if separate |
| Card security code | `text` | `cc-csc` | `inputmode="numeric"` |
| Quantity, age, amount | `number` | none | `type="number"` is for values you count or calculate |

Rules:

1. **Two addresses on one page.** Prefix tokens with `shipping` or `billing`: `shipping postal-code`, `billing postal-code`.
2. **Never disable autofill on sign-in fields.** MDN states that `autocomplete="off"` often will not stop password managers and browsers may ignore it on login fields. Turning it off only hurts the users who can still use it.
3. **Use `off` only for one-off values that are not the user's data**, such as a CAPTCHA answer or a honeypot field.
4. **If a field must not take the user's own saved data** (an admin entering another person's email), use a token the browser does not recognise for `autocomplete` and give `name` and `id` the same unrecognised value. Adam Silver reported in 2021 that this stopped Chrome filling the field. It depends on browser behaviour, so **verify before relying on this** in your target browsers.
5. **Never invent tokens.** A made-up value such as `zip` defeats autofill and fails WCAG 2.2 success criterion 1.3.5, Identify Input Purpose.

### Step 5: Validation

Apply these rules in order.

1. **The server validates every field.** Always. Client checks are for speed and help only. Reuse the project's existing schema or validation library on both sides. A second library lets client and server rules drift apart (the benji forms skill makes the same point).
2. **Accept reasonable formats.** Strip spaces from postcodes, card numbers and phone numbers. Accept upper or lower case. Ignore stray punctuation that does not change the meaning.
3. **Choose one timing model for the whole form.**

| Situation | Model |
|---|---|
| Default | Validate when the user submits. Redisplay the page with every entry kept, an error summary at the top with focus moved to it, `Error: ` at the start of the page `<title>`, and a message beside each field. This is the GOV.UK Design System pattern |
| Feedback that adds information as the user types | Live feedback is fine: character counts, password rules met so far |
| The design system already validates when a field loses focus | Keep it for consistency. Never show an error before the user has left the field once, and never validate per keystroke |
| Cross-field rules (confirm password, end date after start date) | Check on submit |

4. **Native messages or custom messages.** Browser error bubbles cannot be styled and use the browser's language, which may not match the page. If the form shows its own messages, put `novalidate` on the `form`, set `aria-invalid="true"` on the field, and link the message with `aria-describedby`. The Constraint Validation API (`validity`, `checkValidity()`, `setCustomValidity()`) still works with `novalidate`.
5. **Error message wording.** Say what is wrong and how to fix it, in the field's own terms. For example: "Enter your email address" when it is empty, and "Enter an email address in the correct format, like name@example.com" when it is malformed. Never rely on colour alone, and never say only "Invalid input".
6. **The error summary.** A list of links at the top of the form, one per error, each moving focus to its field. Use the same wording as the message beside the field.

### Step 6: Buttons and submission

1. Every button has an explicit `type`. Inside a `form`, a button with no type submits it.
2. One primary submit button per form, with a label that says what happens: "Create account", "Pay £24.00".
3. Guard against double submission by ignoring repeat submits while a request is in flight. Keep the button enabled until then, because a disabled button cannot explain itself to a keyboard or screen reader user.

### Step 7: Plan, check dependants and apply in batches

1. Turn the field table into a change list: file and line, current markup, new markup, rule.
2. For each `name`, `id` and class touched, search the server code, tests and analytics configuration (commands in Templates). Any `name` change goes to the user under guardrail 2.
3. Group the changes into batches of at most 2 forms or 30 field changes.
4. Gate 2: show each batch's diff and wait for a yes before applying it.

### Step 8: Verify

1. **Checker.** Re-run `forms_check.py`. No errors in the changed forms and no new warnings.
2. **Autofill.** In a test browser profile with fake saved details, fill each form from the browser's suggestions. Every field fills with the right value. Sign-in offers the saved test password.
3. **Keyboard.** Tab order follows the visual order. Enter in a text field submits the form. Radios move with arrow keys inside their group.
4. **Errors.** Submit the form empty in the test environment. The summary appears, receives focus, its links move to the fields, the title starts with `Error: `, and every entry is kept.
5. **Screen reader.** Each field announces its label, hint and required state. Each error is announced with its field.
6. **Zoom.** At 200% zoom and a 320 CSS pixel wide window, nothing is cut off and labels stay beside their fields. (The nmor accessible-forms skill uses the same check.)
7. **Server.** Submit invalid data with client checks bypassed (for example, `novalidate` added in devtools) in the test environment. The server rejects it with the same messages.

### Step 9: Report

Use the report template. State fields changed, server gaps, names left for the user, and what was not checked.

## Worked example

The checkout form below is invented. The checker output is real: it was produced by running the script in this skill on these files.

**Input.** A guest checkout in plain HTML. The server validates every field. Analytics reads the `name` attributes. The team has error message and summary components.

```html
<form action="/checkout" method="post">
  <input type="text" name="fullname" placeholder="Full name">
  <label>Email <input type="text" name="email" autocomplete="off"></label>
  <label for="phone">Phone</label>
  <input type="number" id="phone" name="phone">
  <label for="pc">Postcode</label>
  <input id="pc" name="postcode" autocomplete="zip">
  <p>Delivery speed</p>
  <input type="radio" name="speed" value="std" id="std"><label for="std">Standard</label>
  <input type="radio" name="speed" value="exp" id="exp"><label for="exp">Express</label>
  <button class="apply-voucher">Apply voucher</button>
  <button>Pay now</button>
</form>
```

**Step 1, checker output** (tabs shown as spaces):

```
checkout_before.html:2   error  no-label                   input fullname (placeholder is not a label)
checkout_before.html:2   warn   missing-autocomplete       fullname: try name
checkout_before.html:3   warn   autocomplete-off-on-login  on a login-type field; browsers may ignore it and it blocks password managers
checkout_before.html:3   warn   type-mismatch              email: type=text, expected email
checkout_before.html:5   warn   missing-autocomplete       phone phone: try tel
checkout_before.html:5   warn   number-for-identifier      phone phone: type=number is for quantities
checkout_before.html:5   warn   type-mismatch              phone phone: type=number, expected tel
checkout_before.html:7   error  bad-autocomplete           "zip" is not a recognised token
checkout_before.html:9   error  group-without-fieldset     2 radio inputs named speed
checkout_before.html:11  warn   button-without-type        defaults to submit inside a form
checkout_before.html:12  warn   button-without-type        defaults to submit inside a form
checkout_before.html: 3 errors, 8 warnings
```

**Steps 2 to 6, decisions.**

| Field | Change | Rule |
|---|---|---|
| fullname | Visible label "Full name", `autocomplete="shipping name"` | Step 3.1, step 4 |
| email | `type="email"`, `autocomplete="email"`, `spellcheck="false"`, `off` removed | Step 4 table and rule 2 |
| phone | `type="tel"`, `autocomplete="tel"` | Step 4 table |
| postcode | `autocomplete="shipping postal-code"` in place of `zip` | Step 4 rule 5 |
| speed | `fieldset` with `legend` "Delivery speed" | Step 3.4 |
| Apply voucher | `type="button"` | Step 6.1 |
| Pay now | `type="submit"` | Step 6.1 |
| Form | `novalidate`, because the team shows its own messages | Step 5.4 |

No `name` changes, so analytics and the server are unaffected. The user approves the batch.

**After:**

```html
<form action="/checkout" method="post" novalidate>
  <label for="fullname">Full name</label>
  <input type="text" id="fullname" name="fullname" autocomplete="shipping name">
  <label for="email">Email address</label>
  <input type="email" id="email" name="email" autocomplete="email" spellcheck="false">
  <label for="phone">Phone number</label>
  <input type="tel" id="phone" name="phone" autocomplete="tel">
  <label for="pc">Postcode</label>
  <input type="text" id="pc" name="postcode" autocomplete="shipping postal-code">
  <fieldset>
    <legend>Delivery speed</legend>
    <input type="radio" name="speed" value="std" id="std"><label for="std">Standard</label>
    <input type="radio" name="speed" value="exp" id="exp"><label for="exp">Express</label>
  </fieldset>
  <button type="button" class="apply-voucher">Apply voucher</button>
  <button type="submit">Pay now</button>
</form>
```

**Step 8, checker output:** `checkout_after.html: 0 errors, 0 warnings`. The user runs the autofill, keyboard and error checks in the test environment and confirms them. The report notes one item for the user: the "Pay now" label could state the amount.

## Templates

### Checker

`scripts/forms_check.py` ships with this skill. Its autocomplete token list follows the WHATWG field names; **verify it against the current spec before relying on it**.

```
python3 scripts/forms_check.py form.html other.html
```

It exits with status 1 if any error is found, so it can run in CI.

### Field table

```
| Field | Purpose | Label | Hint | type | inputmode | autocomplete | Required | Client rule | Server rule | Error messages |
|---|---|---|---|---|---|---|---|---|---|---|
| email | Contact | Email address | We'll send your receipt here | email | | email | yes | format | format | Enter your email address / Enter an email address in the correct format, like name@example.com |
```

### Field with hint and error

```html
<div class="field field--error">
  <label for="email">Email address</label>
  <p id="email-hint" class="hint">We'll send your receipt here</p>
  <p id="email-error" class="error-message">
    <span class="visually-hidden">Error:</span> Enter an email address in the correct format, like name@example.com
  </p>
  <input type="email" id="email" name="email" autocomplete="email" spellcheck="false"
         aria-describedby="email-hint email-error" aria-invalid="true">
</div>
```

### Error summary

```html
<div class="error-summary" role="alert" tabindex="-1">
  <h2>There is a problem</h2>
  <ul>
    <li><a href="#email">Enter an email address in the correct format, like name@example.com</a></li>
    <li><a href="#pc">Enter your postcode</a></li>
  </ul>
</div>
```

Move focus to the summary when it appears, and prefix the page title: `<title>Error: Checkout</title>`.

### Finding dependants

```
grep -rnE "name=['\"]?(email|phone|postcode)" --include=*.py --include=*.rb --include=*.php --include=*.js --include=*.ts .
grep -rnE "(email|phone|postcode)" --include=*.test.* --include=*.spec.* .
```

Replace the field names with the ones being changed.

### Report

```
HTML FORMS REPORT: [forms], [date], branch [name]

Server validation: [confirmed for all fields / gaps: list]
Baseline: [n] errors, [n] warnings
Fixed: labels [n], autocomplete [n], types [n], grouping [n], buttons [n]
Timing model: [submit / existing blur pattern]
Names left for the user: [list, or none]
Fields to question (may not be needed): [list]
Verification: checker [0 errors], autofill [pass/fail], keyboard [pass/fail],
              error flow [pass/fail], screen reader [pass/fail], zoom [pass/fail], server [pass/fail]
Not checked: [custom widgets, live site, browsers not tested]
Legal wording (consent, privacy) routed to: [person]
```

### Starting prompt

```
Use the html-forms skill on [form files]. It is a [purpose] form built with
[stack]. The server validates in [path]. Start with the checker and the field
table, and do not change any name attributes or files until I approve.
```

## Failure modes and anti-patterns

| # | Failure | How to spot it | What to do instead |
|---|---|---|---|
| 1 | Placeholder used as the label | The checker flags `no-label` with a placeholder note; the field is blank-looking once typed in | A visible `label` above the field. Move format help into a linked hint |
| 2 | A `name` renamed during clean-up | Submissions arrive empty on the server, or analytics loses a field | Never rename `name` without the user's yes and a matching server change. Change `id` and labels freely |
| 3 | Fighting autofill | `autocomplete="off"` on sign-in fields, `readonly` hacks, random tokens on ordinary fields | Correct tokens on every personal field. Reserve the unrecognised-token approach for fields that must not take the user's own data |
| 4 | Client-only validation | Disabling scripts or adding `novalidate` lets bad data through | Server rules for every field, tested in step 8.7 |
| 5 | Validation that punishes | Errors appear while the user is still typing, messages say "Invalid", or only a red border marks the error | Submit-time validation with a summary and specific messages, or the existing blur pattern with no error before the first blur |

## Quality bar

Check every item before handing over.

1. The user approved gate 1, and the work is on a named branch.
2. The baseline checker output is saved and quoted in the report.
3. A field table exists with every cell filled or marked "none".
4. Every control has a visible label linked to it.
5. Every personal-data field has a recognised `autocomplete` token, and no sign-in field has `off`.
6. No identifier field (phone, postcode, card) uses `type="number"`.
7. Every radio or checkbox group with one name sits in a `fieldset` with a `legend`.
8. Every button has an explicit `type`.
9. The report states whether the server validates every field.
10. No `name` attribute changed without the user's recorded yes.
11. The checker shows zero errors on the changed forms.
12. The error flow test passed: summary, focus, title prefix and kept values.
13. The report and examples contain no real personal data, passwords, codes or card numbers.

## Cost and effort

The checker runs locally and makes no network calls. The agent reads its output and the form markup only, so token use stays small. The cheapest path for a large site is to fix the shared field and error components once, then re-run the checker on every page that uses them.

## Handoffs to related skills

`semantic-html.md` covers buttons, links, headings and landmarks outside forms. `form-optimisation.md` and `signup-flow-optimisation.md` cover cutting fields and raising completion rates. `checkout-optimisation.md` covers the wider checkout journey. `ai-security-privacy.md` covers what form data may go to AI tools.

## Appendix: tool-specific notes

Every fact below was read on 23 October 2026. **Verify before relying on this**: browsers and specs change.

### autocomplete (MDN)

- Values are `on`, `off` or an ordered list of tokens. Controls without the attribute inherit the value from their `form`.
- A leading `section-` token groups fields that share autofill data. `shipping` and `billing` separate duplicate fields.
- `off` often will not stop password managers from offering to save or fill credentials, and browsers may ignore it on login fields.
- Hidden inputs cannot use `on` or `off`.
- WCAG 2.2 SC 1.3.5 (Level AA) requires the purpose of user-information fields to be programmatically determinable. Valid tokens help meet it, and invalid tokens fail it.

### Constraint validation (MDN)

- Built-in attributes: `required`, `minlength`, `maxlength`, `min`, `max`, `step`, `type` and `pattern`.
- `:user-valid` and `:user-invalid` match only after the user has interacted with the field.
- `setCustomValidity("")` clears a custom error. Clear it on every `input` event, or the field stays invalid.
- `novalidate` turns off the browser's bubbles and keeps the API and pseudo-classes working.
- Native bubbles cannot be styled and use the browser's language.

### Autofill (web.dev)

- Browsers save values by `name`, and some also by `id`.
- `autocomplete="one-time-code"` is supported from Safari 14. On Chrome for Android, the WebOTP API covers SMS codes.
- Use native controls such as `select` for card expiry. Custom elements may not support autofill.
- `:autofill` styles autofilled fields.
- The page was last updated on 3 November 2021, so check current browser support.

### GOV.UK Design System validation pattern

Validate on submit, not when a field loses focus. Redisplay with entries kept, prefix the title with `Error: `, show an error summary with focus moved to it, and place a message beside each field. Always validate on the server. GOV.UK services turn off HTML5 validation with `novalidate` and do not use the `required` attribute. The team lists client-side validation as an open research question.

### Chrome and autocomplete="off"

Adam Silver (4 February 2021) reported that Chrome stopped filling a field when `name`, `id` and `autocomplete` were all set to a value it did not recognise, and that `new-password` on all three stopped autofill on create-password fields. A 2019 post by Leonardo Faria reported `off` and `new-password` failing for him, and a `readonly`-until-focus workaround. Both depend on browser versions. Test in your target browsers.

### Claude Agent Skills

Anthropic limits `name` to 64 characters and `description` to 1,024. Claude Code documents `disable-model-invocation: true` for skills with side effects, so they run only when the user calls them. Install skills only from sources you trust.

## Sources

Last verified 23 October 2026 for every source below, except where a date says otherwise.

- MDN, HTML attribute: autocomplete: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete.
- MDN, Client-side form validation: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Form_validation.
- W3C WAI, Forms tutorial (updated 27 March 2026): https://www.w3.org/WAI/tutorials/forms/.
- WebAIM, The WebAIM Million (2026 report): https://webaim.org/projects/million/. 51% of home pages with missing form input labels.
- GOV.UK Design System, Recover from validation errors: https://design-system.service.gov.uk/patterns/validation.
- web.dev, Autofill (Learn Forms, last updated 3 November 2021): https://web.dev/learn/forms/autofill.
- web.dev, Sign-in form best practices codelab: https://web.dev/articles/codelab-sign-in-form-best-practices.
- Adam Silver, Stopping Chrome from ignoring autocomplete=off (4 February 2021): https://adamsilver.io/blog/stopping-chrome-from-ignoring-autocomplete-off/.
- Leonardo Faria, Disabling autofill in Chrome (2 October 2019): https://dev.to/leonardofaria/disabling-autofill-in-chrome-368k.
- Nmor, the-claude-council, accessible-forms skill (via Skills Directory): https://www.skillsdirectory.com/skills/nmor-accessible-forms. Rival implementation.
- agentsorg, benji, forms skill (via skills.sh): https://www.skills.sh/agentsorg/benji/forms. Rival implementation.
- joabgonzalez, ai-agents-skills, html skill (via skills.cat): https://skills.cat/skills/joabgonzalez/ai-agents-skills/html. Rival implementation.
- Anthropic, Agent Skills best practices (read 22 October 2026): https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices.
- Claude Code documentation, Skills (read 22 October 2026): https://code.claude.com/docs/en/skills.

---
Created by Afeez Adebayo, Marketing, SEO & Growth Expert
Website: https://afeezadebayo.com
LinkedIn: https://www.linkedin.com/in/afeez-adebayo/
X: https://x.com/za_icon
Source: https://afeezadebayo.com/skills-md/frontend-development/html-semantics/html-forms/

Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). You may copy, share and adapt this skill, including for commercial use, as long as you credit Afeez Adebayo and link to the source URL above. Keep this notice in any copy or adapted version.
