Lesson 9 of 12

HTML Forms

Forms are how websites listen. Every search box, login screen, contact page and checkout is a form. HTML forms give you a rich set of controls with built-in keyboard support and validation, so you can build a lot before writing any JavaScript. This lesson builds on HTML Tables and the attribute rules from HTML Elements and Attributes.

The form element

All the controls in a form sit inside a <form> element. Two attributes decide what happens when the user submits it:

  • action – the URL that receives the data, usually a server-side script.
  • method – how the data is sent. get adds it to the URL (good for searches you might bookmark); post sends it in the body of the request (use it for anything that changes data or is private, such as sign-ups and passwords).

Processing the data on the server is a separate topic, covered in PHP Forms: GET and POST. In this lesson the examples use a few lines of JavaScript to display the submitted data on the page instead of sending it anywhere.

Labels: the most important part

Every control needs a visible <label>. Connect them by giving the input an id and the label a matching for attribute. The connection means screen readers read the label when the field is focused, and clicking the label puts the cursor in the field – a bigger target that helps everyone on touch screens.

Each control also needs a name. The name is the key the data is sent under; a field without a name is not submitted at all.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Enquiry form</title>
  <style>
    body { font-family: sans-serif; }
    label, legend { font-weight: bold; }
    p > label { display: block; }
    p { margin: 0.6rem 0; }
    output { display: block; margin-top: 1rem; padding: 0.5rem; background: #eef6ee; white-space: pre-line; }
  </style>
</head>
<body>
  <form>
    <p>
      <label for="full-name">Full name</label>
      <input type="text" id="full-name" name="fullname" autocomplete="name" required>
    </p>
    <p>
      <label for="email">Email</label>
      <input type="email" id="email" name="email" autocomplete="email" required>
    </p>
    <p>
      <label for="phone">Mobile number</label>
      <input type="tel" id="phone" name="phone" autocomplete="tel">
    </p>
    <button type="submit">Send enquiry</button>
  </form>
  <output id="out"></output>

  <script>
    document.querySelector('form').addEventListener('submit', function (event) {
      event.preventDefault();
      const data = new FormData(event.target);
      let text = 'Submitted data:\n';
      for (const [name, value] of data) {
        text += name + ' = ' + value + '\n';
      }
      document.getElementById('out').textContent = text;
    });
  </script>
</body>
</html>

Watch out: A placeholder is not a label. Placeholder text disappears as soon as someone types, often has poor contrast, and is not reliably announced. Use it only for an extra hint, such as an example format.

Input types

The type attribute changes how an <input> behaves. Choosing the right type in HTML forms gives mobile users the right keyboard and gives you free validation.

TypeUse for
textShort free text such as a name
emailEmail addresses; checks the format and shows an @ keyboard on phones
telPhone numbers; shows a number pad (no format check, as formats vary by country)
passwordPasswords; hides the characters
numberQuantities, with min, max and step
dateDates, with a native date picker
searchSearch boxes
checkbox / radioYes/no choices and one-from-many choices

The autocomplete attribute (for example autocomplete="email") lets browsers fill in saved details, which saves typing and reduces errors.

Choices: radio buttons, checkboxes and select

Radio buttons let the user pick exactly one option. Give every radio in the group the same name and a different value. Checkboxes allow any number of selections. A <select> drop-down is useful when there are many options, and <textarea> accepts several lines of text.

Group related controls in a <fieldset> with a <legend>. The legend acts as a label for the whole group, so a screen reader announces “Room type” before “Single” or “Double”.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Booking form</title>
  <style>
    body { font-family: sans-serif; }
    label, legend { font-weight: bold; }
    p > label { display: block; }
    p { margin: 0.6rem 0; }
    output { display: block; margin-top: 1rem; padding: 0.5rem; background: #eef6ee; white-space: pre-line; }
    fieldset { margin-bottom: 0.8rem; }
  </style>
</head>
<body>
  <form>
    <fieldset>
      <legend>Room type</legend>
      <label><input type="radio" name="room" value="single" required> Single</label>
      <label><input type="radio" name="room" value="double"> Double</label>
      <label><input type="radio" name="room" value="family"> Family</label>
    </fieldset>

    <fieldset>
      <legend>Extras</legend>
      <label><input type="checkbox" name="extras" value="breakfast"> Breakfast</label>
      <label><input type="checkbox" name="extras" value="pickup"> Airport pick-up</label>
    </fieldset>

    <p>
      <label for="city">City</label>
      <select id="city" name="city">
        <option value="">Choose a city</option>
        <option value="pondicherry">Pondicherry</option>
        <option value="madurai">Madurai</option>
        <option value="kochi">Kochi</option>
      </select>
    </p>

    <p>
      <label for="notes">Special requests</label>
      <textarea id="notes" name="notes" rows="3" cols="30"></textarea>
    </p>

    <button type="submit">Check availability</button>
  </form>
  <output id="out"></output>

  <script>
    document.querySelector('form').addEventListener('submit', function (event) {
      event.preventDefault();
      const data = new FormData(event.target);
      let text = 'Submitted data:\n';
      for (const [name, value] of data) {
        text += name + ' = ' + value + '\n';
      }
      document.getElementById('out').textContent = text;
    });
  </script>
</body>
</html>

Tip: Wrapping the input inside its <label>, as with the radio buttons above, is another valid way to connect them. No for or id is needed in that case.

Buttons

Use <button type="submit"> to submit a form. Always state the type: a button inside a form is a submit button by default, which surprises people who meant it for something else. Use type="button" for buttons that run JavaScript, and avoid type="reset", which wipes everything the user typed with one accidental click.

Built-in validation in HTML forms

Browsers can check what people type before the form is submitted, with no JavaScript. Try submitting the form below with empty or wrong values.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Validation</title>
  <style>
    body { font-family: sans-serif; }
    label, legend { font-weight: bold; }
    p > label { display: block; }
    p { margin: 0.6rem 0; }
    output { display: block; margin-top: 1rem; padding: 0.5rem; background: #eef6ee; white-space: pre-line; }
  </style>
</head>
<body>
  <form>
    <p>
      <label for="pin">PIN code (6 digits)</label>
      <input id="pin" name="pin" inputmode="numeric" pattern="[0-9]{6}" required>
    </p>
    <p>
      <label for="guests">Guests (1–6)</label>
      <input type="number" id="guests" name="guests" min="1" max="6" value="2">
    </p>
    <p>
      <label for="pass">Password (at least 8 characters)</label>
      <input type="password" id="pass" name="password" minlength="8" autocomplete="new-password" required>
    </p>
    <button type="submit">Register</button>
  </form>
  <output id="out"></output>

  <script>
    document.querySelector('form').addEventListener('submit', function (event) {
      event.preventDefault();
      const data = new FormData(event.target);
      let text = 'Submitted data:\n';
      for (const [name, value] of data) {
        text += name + ' = ' + value + '\n';
      }
      document.getElementById('out').textContent = text;
    });
  </script>
</body>
</html>
  • required – the field cannot be empty.
  • minlength / maxlength – limits on the number of characters.
  • min / max – limits for numbers and dates.
  • pattern – a regular expression the value must match.

Watch out: Browser validation is for convenience only. Anyone can bypass it, so the server must always check the data again. This is covered in PHP Forms: GET and POST.

You can style HTML forms with CSS and add custom behaviour with JavaScript Events. Next, you will learn how to give your whole page a meaningful structure in HTML Semantic Elements.

Further reading

Key takeaways

  • Wrap controls in <form>; action sets where data goes and method sets get or post.
  • Every control needs a visible <label> and a name; placeholders are not labels.
  • Pick the right input type for better keyboards and free validation.
  • Group related radios and checkboxes in a <fieldset> with a <legend>.
  • Use required, min, max, minlength and pattern for validation – and always validate again on the server.

Quick quiz

Check what you learned – pick an answer.

1. How do you connect a label to <input id="city">?

2. What makes radio buttons work as one group where only one can be chosen?

3. Why must the server validate data even when the form uses required and pattern?

Practice exercise

Build a feedback form with a required name field, an email field, a 1–5 rating using radio buttons inside a fieldset with the legend "Rating", a textarea for comments and a submit button.

Show a hint

All five radio buttons need the same name, e.g. name="rating", and different values.

Show the solution
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Feedback form</title>
  <style>
    p > label { display: block; }
  </style>
</head>
<body>
  <form action="/feedback" method="post">
    <p>
      <label for="name">Name</label>
      <input type="text" id="name" name="name" autocomplete="name" required>
    </p>
    <p>
      <label for="email">Email</label>
      <input type="email" id="email" name="email" autocomplete="email">
    </p>
    <fieldset>
      <legend>Rating</legend>
      <label><input type="radio" name="rating" value="1" required> 1</label>
      <label><input type="radio" name="rating" value="2"> 2</label>
      <label><input type="radio" name="rating" value="3"> 3</label>
      <label><input type="radio" name="rating" value="4"> 4</label>
      <label><input type="radio" name="rating" value="5"> 5</label>
    </fieldset>
    <p>
      <label for="comments">Comments</label>
      <textarea id="comments" name="comments" rows="4" cols="40"></textarea>
    </p>
    <button type="submit">Send feedback</button>
  </form>
</body>
</html>

Progress is saved in this browser only.