HTML Forms

Forms & Validation

Forms are how users send data to your app. Labels, the right input types and native validation get you most of the way before you write any JavaScript.

beginner14 min readUpdated Sep 15, 2026
signup.html
html
<!-- signup.html -->
<form action="/signup" method="post">
  <label for="email">Email address</label>
  <input id="email" name="email" type="email"
         autocomplete="email" required />

  <label for="password">Password</label>
  <input id="password" name="password" type="password"
         minlength="8" required />

  <button type="submit">Create account</button>
</form>
Wrapper
form element
Key attribute
name
Labelling
label with for
Types
email, tel, number, date
Validation
Native constraints

Why it matters

Why forms deserve care

Usable by everyone

Proper labels, grouping and error messages make forms work with screen readers and keyboards.

Validated twice

Native validation improves the experience; server validation protects the data.

The right control

Correct input types give users the right keyboard and enable built-in checks.

The big picture

The three parts of a good form

A clear label, the right control type and honest validation that helps instead of blocking.

Structure

Markup

form, label, input, fieldset and legend describe the data you collect.

Labelling

Clarity

Every control needs a label associated with it, visible and programmatic.

Validation

Guard

Native constraints plus server checks keep bad data out.

Forms at a glance

The core of forms

form and controls

form wraps inputs, selects, textareas and buttons.

Input types

email, tel, number, date, url and more change keyboard and validation.

Labels

Associate every control with a label using for and id.

Fieldsets

Group related controls with fieldset and legend.

Constraints

required, minlength, pattern, min, max and step.

Errors

Explain what is wrong and how to fix it, near the field.

A short history

From basic inputs to rich constraints

  1. 1993

    The first forms

    HTML forms let pages submit data to a server.

    93
  2. 1999

    HTML4 controls

    A stable set of inputs, selects and textareas becomes standard.

    99
  3. 2011

    HTML5 input types

    email, number, date, range and others arrive with native validation.

    11
  4. 2014

    Constraint validation API

    Scripts gain access to validity state and custom messages.

    14
  5. Today

    Better UX by default

    Correct types and attributes give mobile keyboards, autofill and checks for free.

    Today

The complete guide

Forms & Validation: Everything you need to know

Why forms deserve care

Forms are where users give you money, sign up, search and send messages. They are also where frustration concentrates: a missing label, a confusing error or an input that opens the wrong keyboard can lose a user in seconds.

The good news is that HTML already solves most of this. With proper labels, the right input types and native constraints, you get accessible, mobile-friendly, validated forms before writing a line of JavaScript.

Form structure

A form wraps its controls and declares where and how to submit them.

<!-- signup.html -->
<form action="/signup" method="post">
  <label for="email">Email address</label>
  <input id="email" name="email" type="email" autocomplete="email" required />

  <label for="password">Password</label>
  <input id="password" name="password" type="password" minlength="8" required />

  <button type="submit">Create account</button>
</form>
  • action is the URL that receives the data.
  • method is get or post.
  • name on each control is the key in the submitted data.
  • type=“submit” triggers submission; a bare button inside a form defaults to submit, so use type="button" for other actions.

Labels

Every control needs a label. A label is announced by screen readers, acts as a larger click target and stays visible after the field is filled.

<!-- label.html -->
<label for="phone">Phone number</label>
<input id="phone" name="phone" type="tel" autocomplete="tel" />

You can also wrap the control inside the label, which associates them implicitly:

<label>
  Subscribe to the newsletter
  <input type="checkbox" name="subscribe" />
</label>

Placeholders are not labels. They disappear once a user types, often have poor contrast and are not reliably announced. Use a real label and, if helpful, a hint.

Input types

Choosing the right type gives users the correct mobile keyboard and enables built-in validation.

Type Use for Benefit
email Email addresses Email keyboard, format check
tel Phone numbers Numeric keypad
number Numeric values Numeric input, min/max/step
url Web addresses URL keyboard, format check
date Dates Date picker
password Secrets Masked input
search Search fields Search keyboard, clear button
file Uploads File picker and type filtering

Use autocomplete with standard tokens like email, given-name, postal-code and new-password so browsers and password managers can help.

Grouping controls

Group related controls so their relationship is clear to everyone.

<!-- address.html -->
<fieldset>
  <legend>Shipping address</legend>

  <label for="street">Street</label>
  <input id="street" name="street" autocomplete="address-line1" />

  <label for="city">City</label>
  <input id="city" name="city" autocomplete="address-level2" />
</fieldset>

fieldset and legend group radio buttons and related fields, which screen readers announce as a set. For radio groups, always use a fieldset with a legend.

Native validation

HTML provides constraint validation through attributes.

<!-- validate.html -->
<input type="email" name="email" required />
<input type="password" name="password" minlength="8" required />
<input type="number" name="age" min="18" max="120" step="1" />
<input type="text" name="handle" pattern="[a-z0-9_]{3,15}" />
<input type="url" name="website" />

The browser blocks submission and shows a message when a constraint fails. You can style the states with CSS:

/* styles.css */
input:invalid { border-color: #dc2626; }
input:valid { border-color: #16a34a; }
input:focus-visible { outline: 2px solid #2563eb; }

Native messages are functional but terse and untranslated. For important forms, provide your own clear messages using the Constraint Validation API:

// validate.js
const email = document.querySelector("#email");

email.addEventListener("invalid", () => {
  email.setCustomValidity("");
  if (email.validity.valueMissing) {
    email.setCustomValidity("Please enter your email address.");
  } else if (email.validity.typeMismatch) {
    email.setCustomValidity("That does not look like an email address.");
  }
});

email.addEventListener("input", () => email.setCustomValidity(""));

Remember to clear the custom message on input, or the field stays invalid.

Accessible errors

Validation is only helpful if users understand what went wrong.

  • Show a clear message in text next to the field, not just a red border.
  • Associate it with the control using aria-describedby.
  • Mark the field with aria-invalid="true".
  • Move focus to the first invalid field on submit.
  • Summarise errors at the top for long forms.
<!-- error.html -->
<label for="email">Email address</label>
<input id="email" name="email" type="email" required
       aria-invalid="true" aria-describedby="email-error" />
<p id="email-error" class="error">Please enter a valid email address.</p>

Never rely on colour alone; pair it with text and an icon.

Always validate on the server

Client-side validation is a user-experience feature, not a security control. Anyone can edit the page, disable JavaScript or send a request directly. The server must validate types, lengths, formats and permissions, and return clear errors the client can display. See the Web Security guide.

Best practices

  • Give every control a visible, associated label.
  • Use the correct input type and autocomplete tokens.
  • Group related controls with fieldset and legend.
  • Prefer native constraints, then enhance with the Constraint Validation API.
  • Show accessible, specific error messages near the field.
  • Validate again on the server for every request.
  • Keep forms short and ask only for what you need.

Common mistakes

  • Using placeholders instead of labels.
  • Missing name attributes, so fields are not submitted.
  • Submitting sensitive data with GET.
  • Relying on client-side validation for security.
  • Communicating errors with colour alone.
  • Resetting the whole form on a validation error and losing input.

Where to go next

Forms combine markup, accessibility and validation. Strengthen the markup with Semantic HTML, make it inclusive with the Accessibility guide, and handle submissions with JavaScript. Then audit one form in your app against the checklist above.

Labelling an input

A visible, associated label is announced by screen readers and remains when the field is filled. A placeholder disappears.

Prefer
<label for="email">Email address</label>
<input id="email" name="email"
       type="email" autocomplete="email" />
Avoid
<input name="email" type="email"
       placeholder="Email address" />

Validating input

Use the native type and constraints, then validate on the server. JavaScript-only checks are bypassed by anyone who wants to.

Prefer
<input type="email" name="email"
       required autocomplete="email" />
<input type="password" name="password"
       minlength="8" required />
Avoid
<input name="email" />
<input name="password" />
<!-- checked only after submit -->

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Forms & Validation?

Our interactive tutorial walks you through Forms & Validation step by step — with quizzes and real code you can run in the browser.