A form is a way for a page to send typed values back to a server. The markup is simple, but two details decide whether it works: the name attribute on every control, and the choice of input type, which decides what keyboard, what validation and what built-in behaviour the visitor gets.
What does the form element do?
The form wraps the controls, names the destination, and says how the data should be sent.
<form action="/subscribe" method="post">
<label for="email">Email address</label>
<input type="email" id="email" name="email" required>
<button type="submit">Subscribe</button>
</form>
The action is the URL that receives the data and the method is how it travels. post puts the values in the body of the request, which is what you want for anything sensitive; get appends them to the URL, which is convenient for a search form and wrong for a password. Without a method, the default is get.
Why does every control need a name?
Because the name is the label the server sees. Submit the form above and the request body contains the value of the control called email. A control with no name is not sent at all — the browser collects it, the user fills it in, and it vanishes.
POST /subscribe
[email protected]
The id has a completely different job: it is the internal identifier that a label points at and that CSS and JavaScript use. The two are independent, and beginners often confuse them. Every control needs a name to be submitted; it needs an id only if something else has to refer to it.
Which input types exist?
The type is the single most valuable attribute on an input, because the browser uses it to give the visitor the right control and the right keyboard.
<input type="text" name="name">
<input type="email" name="email">
<input type="password" name="password">
<input type="number" name="quantity" min="1" max="99" step="1">
<input type="date" name="start-date">
<input type="time" name="reminder">
<input type="url" name="website">
<input type="tel" name="phone">
<input type="search" name="q">
<input type="color" name="shade">
<input type="file" name="upload" accept="image/*">
<input type="checkbox" name="topics" value="java">
<input type="radio" name="plan" value="free">
email,url,telgive the right keyboard on a phone and a basic format check on a desktop.numbergives a numeric keypad, andmin,maxandstepconstrain the value.dateandtimegive a picker, which removes every date-formatting problem at once.colorgives a swatch picker and returns a hex value.fileopens the system file dialog; theacceptattribute filters the types shown.
Two rules keep the behaviour predictable. Use type="text" as the fallback and never leave the attribute off, because the default type behaves differently from what the visitor expects when they tab into the field. And if you add a pattern constraint, always pair it with a visible example, because a pattern the visitor cannot see is a form they cannot complete.
What about longer text and multiple choices?
Two more controls cover the remaining cases. A textarea takes multi-line text, and a select takes one choice from a list. Both send their value with the name of the control, exactly like an input.
<label for="message">Your message</label>
<textarea id="message" name="message" rows="5" cols="40" maxlength="500"></textarea>
<label for="country">Country</label>
<select id="country" name="country">
<option value="" selected disabled>Choose one</option>
<option value="uk">United Kingdom</option>
<option value="ie">Ireland</option>
</select>
Note the selected disabled option in the select: it is the placeholder that shows before the visitor chooses, and disabling it stops it being a valid submission, which is how you make "choose one" mean something.
Why does autocomplete matter so much?
Typing the same details into a dozen forms is the single most irritating thing about being online, and the autocomplete attribute fixes it. The browser stores what the visitor has entered before and offers it again — the name, the email address, the card number, the address — so the form arrives pre-filled on the next site.
<input type="text" name="name" autocomplete="name">
<input type="email" name="email" autocomplete="email">
<input type="tel" name="phone" autocomplete="tel">
<input type="text" name="street" autocomplete="street-address">
<input type="text" name="postcode" autocomplete="postal-code">
<input type="password" name="password" autocomplete="new-password">
The token names are defined by the browser, and using the right one is what makes the suggestion appear. A field with a made-up token gets no help, so it is worth learning the handful you will actually use: name, email, tel, username, new-password, current-password, street-address, postal-code, country and one-time-code.
Use autocomplete="off" only when a field genuinely should not be remembered, and never on a login form — turning it off there breaks password managers, and visitors end up typing passwords by hand.
What are hidden fields for?
A hidden input carries a value to the server without showing it to the visitor. It is the standard way to send state that the server needs but the visitor does not fill in, such as which form was submitted, a campaign identifier, or a value the browser computed.
<input type="hidden" name="form_id" value="newsletter-2026">
<input type="hidden" name="referrer" value="pricing-page">
Because the visitor can edit a hidden field, treat its contents as untrusted. It is never a place for a secret, a password or anything you would be upset to see tampered with — the server has to validate it like any other value.
Quick reference
- The form needs an
actionand amethod;postfor anything sensitive. - Every control needs a
nameto be submitted; theidis for labels, CSS and JavaScript. - Choose the input type that matches the data, and the mobile keyboard and validation follow.
type="text"is the fallback — never omit the type attribute.- A disabled placeholder option stops an empty select being submitted as a real answer.
Try it yourself: build a signup form with a name, an email, a quantity, a start date and a choice of plan. Submit it to a local server and read the request body in the server log. Then remove the name from one field, submit again, and watch that value disappear from the request.