Every website you have ever signed into, ordered food from, or subscribed to has one thing in common: an HTML form. Forms are how websites collect information from real human beings. Once you understand forms, you can build login pages, contact forms, search boxes, checkout flows, surveys — basically any interactive thing on the internet.

This article picks up where HTML for Beginners left off. We assume you are comfortable with the basic HTML tags and want to learn the new ones forms introduce: <form>, <input>, <label>, <button>, and a handful of others.

What is a form, really?

An HTML form is a group of input controls wrapped in a <form> tag. When the user fills in the controls and clicks a submit button, the browser bundles up the answers and sends them to a server. The server reads them, does something with them (creates an account, sends an email, processes a payment), and sends back a response.

Forms have three layers working together. The HTML defines what inputs exist and what they mean. The CSS styles them so they look like part of your design. The JavaScript validates them, gives feedback, and (in modern apps) submits them without a full page reload. We will mostly focus on the HTML layer in this article, with quick nods to the other two.

The anatomy of a form

Every form starts with a <form> tag and ends with a matching </form>. Inside, you put a series of <input> (or related) elements, plus a <button> to submit. The two most important attributes on the form tag itself are:

  • action — the URL the form submits to. If you omit it, the form submits to the current page.
  • method — usually GET or POST. GET puts the data in the URL (good for searches). POST sends it in the request body (good for anything that changes server state, like creating an account).

Here is the simplest possible form:

&lt;form action="/search" method="GET"&gt;
  &lt;input name="q" placeholder="Search..."&gt;
  &lt;button&gt;Search&lt;/button&gt;
&lt;/form&gt;

When the user types "mango" and clicks Search, the browser navigates to /search?q=mango. That is it. You have just built a working search box.

Input types you will use every day

The <input> tag is the workhorse of forms. You change its behaviour with the type attribute. Here are the types you will reach for ninety percent of the time:

  • text — a plain text box. The default if you do not specify a type.
  • email — a text box that validates the value looks like an email address.
  • password — a text box that masks the characters as dots. The browser will also refuse to autofill it on pages without HTTPS.
  • number — a numeric input with up/down arrows.
  • checkbox — a tickable box. Multiple can be checked at once.
  • radio — a tickable circle. Multiple with the same name form a group where only one can be selected.
  • date — a date picker. Renders as a native calendar on most browsers.
  • submit — a button that submits the form. (You can also use <button type="submit">, which is usually cleaner.)
  • file — a button that opens a file picker. Used for uploads.

For longer text, use <textarea> instead of <input>. For dropdowns, use <select> with <option> children.

Labels: the most underrated form element

Every input should have a label. A label is a small piece of text that tells the user what the input is for. You write it with <label> and link it to the input with the for attribute:

&lt;label for="email"&gt;Email address&lt;/label&gt;
&lt;input name="email" type="email"&gt;

The for attribute must match the id of the input. When that link is in place, three good things happen: the user can click the label to focus the input; screen readers announce the label when the input gets focus; and the browser's autofill works correctly.

If you skip labels, your form will technically work but it will be inaccessible. There is no excuse not to use them. They take ten extra characters per input and dramatically improve the experience.

Validation: making sure the data is sane

Forms let users type anything they want, which means you have to protect your server from bad input. The browser can do the first pass for you with built-in validation. Here are the most useful attributes:

  • required — the field must not be empty.
  • type="email" — the value must look like an email.
  • minlength and maxlength — minimum and maximum number of characters.
  • min and max — minimum and maximum numeric value.
  • pattern — a regular expression the value must match.

When a user submits a form with invalid data, the browser blocks the submission and shows a friendly error message — without you writing a single line of JavaScript. For more advanced validation, see our Form UX article.

A real example: a contact form

Let us build a real, working contact form. Save this as contact.html:

&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;Contact us&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;Contact us&lt;/h1&gt;
  &lt;form action="/messages" method="POST"&gt;
    &lt;p&gt;
      &lt;label for="name"&gt;Your name&lt;/label&gt;
      &lt;input name="name" type="text" required&gt;
    &lt;/p&gt;
    &lt;p&gt;
      &lt;label for="email"&gt;Email&lt;/label&gt;
      &lt;input name="email" type="email" required&gt;
    &lt;/p&gt;
    &lt;p&gt;
      &lt;label for="message"&gt;Message&lt;/label&gt;
      &lt;textarea name="message" required&gt;&lt;/textarea&gt;
    &lt;/p&gt;
    &lt;button type="submit"&gt;Send&lt;/button&gt;
  &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;

When the user fills this in and clicks Send, the browser navigates to /messages with a POST request. The body contains the three values, URL-encoded. A real back-end would read them, save them to a database, send an email, and redirect back to a thank-you page.

Common pitfalls

  • Forgetting the name attribute. Without name, the input's value is not included in the submission. You will get an empty form and have no idea why.
  • Using GET for sensitive data. Passwords, credit cards, anything personal — always POST. GET puts the values in the URL bar and the browser history.
  • Submitting without HTTPS. Plain HTTP means anyone on the network can read the form data. Use HTTPS in production, full stop.
  • Not labelling inputs. Already covered, but it is worth repeating. Every input needs a label.
  • Putting required on the wrong things. Optional fields should not have required. A checkbox group where "at least one" is required needs a tiny script — the built-in required only checks individual inputs.

A more advanced thing: form submission with JavaScript

By default, submitting a form reloads the page. Modern apps usually want to submit without a reload, show a spinner, handle errors, and clear the form on success. The pattern looks like this:

const form = document.querySelector("#contact");
const status = document.querySelector("#status");

form.addEventListener("submit", async (e) =&gt; {
  e.preventDefault();              // stop the page reload
  status.textContent = "Sending...";
  const data = new FormData(form); // collect values
  try {
    const res = await fetch(form.action, {
      method: form.method,
      body: data,
    });
    if (!res.ok) throw new Error("HTTP " + res.status);
    status.textContent = "Sent. Thanks!";
    form.reset();
  } catch (err) {
    status.textContent = "Failed: " + err.message;
  }
});

The FormData object collects every named input in the form and packages it the way a normal submission would. Your server does not need to know whether it was submitted by JavaScript or a plain old form post — the format is identical.

A more advanced thing: p

Further reading

Forms look simple until you try to build one that survives real users. The references below are the ones the Mangobaz team reaches for first — they cover the DOM API, the browser’s built-in validation, and the underlying W3C specification.

  • MDN HTMLFormElement — the reference for the form element itself, including its properties, methods, and the events you should be listening for.
  • MDN client-side form validation — the official guide to constraint validation, the Constraint Validation API, and writing usable error messages.
  • W3C HTML forms specification — the W3C specification that defines form submission semantics, valid controls, and the form-associated element lifecycle.
rogressive enhancement

The most useful technique in form design is "progressive enhancement": make the form work without JavaScript first, then layer JavaScript on top to make it nicer. This means the form still works for users with JS disabled, in old browsers, or when the network is down — and the JS version is an enhancement, not a requirement.

Concretely: build your form with a real action and method attribute, and a real submit button. Add a <noscript> warning if JavaScript is required. Then attach a submit handler in JavaScript that calls e.preventDefault() and submits via fetch. If JS is disabled or fails, the form still submits the old-fashioned way. The user always wins.

Should I use a form library?

For a single contact form, no — vanilla HTML and a few lines of JavaScript are enough. For a large app with dozens of forms, validations, multi-step flows, and conditional fields, libraries like React Hook Form, Formik, or VeeValidate save a lot of typing. Pick them when you have a real reason, not before.

FAQ

What is the difference between GET and POST?

GET puts the form data in the URL as query parameters. POST puts it in the request body, which does not appear in the URL. Use GET for searches, filters, and anything idempotent. Use POST for anything that changes server state — logins, signups, payments, contact forms.

Why does my form reload the whole page?

That is the default behaviour. The form is submitting the old-fashioned way, which navigates the browser to the action URL. To submit without reloading, call e.preventDefault() inside the submit handler and use fetch to send the data manually.

How do I make a dropdown?

Use <select> with <option> children. Each option has a value attribute that becomes the submitted value, and text content that the user sees. You can group options with <optgroup>.

Can I have a form inside another form?

No. HTML does not allow nested forms, and browsers handle it inconsistently. If you need separate submission groups, use one form and split it with JavaScript, or use buttons with form="..." attributes that point to different forms by id.

How do I disable the browser's autofill?

Add autocomplete="off" to the input. Use sparingly — most users want autofill. Only turn it off for genuinely sensitive one-time entries like a new password.

Why is my form's submit button not working?

The button is probably outside the form. Make sure it is between the opening and closing <form> tags. If you cannot move it, give the form an id and add form="that-id" to the button — that links them across the DOM.

How do I make a multi-step form?

Hide all but the first step in your markup, then reveal each subsequent step when the user submits the current one. You can do this with vanilla JavaScript, a state management library, or a tiny framework like Alpine.js. For a real-world example, see Form UX for Beginners.

Homework

Build a sign-up form for a fictional newsletter called "Mango Weekly". Save the file as signup.html. The form must include:

  • A text input for the user's full name, required, with a label.
  • An email input, required, with a label and a minlength of 5.
  • A radio group with three options: "Weekly", "Bi-weekly", "Monthly". The user must pick one (hint: required on a radio works only if at least one option has it).
  • Three checkboxes for topic interests: "Coding", "Design", "Product". All optional.
  • A submit button labelled "Subscribe".
  • Style it nicely with CSS from our earlier articles.

When you are done, fill it out and submit. Watch the URL bar (because there is no back-end yet) to see the values the browser tried to send. That is what your future server endpoint will receive. If you feel ambitious, add the JavaScript snippet from the article to submit without reloading and show a thank-you message.