Forms
In short: An HTML form collects data in labelled fields with a name attribute and sends them with a button, while required, type, min, max and pattern check them in the browser.
Problem
An order with no data
The shop gets orders with no name and no phone. The buyers swear they filled in everything. Here is the checkout form. Fill it in and press "Place order".
Console
The sandbox sends nothing over the network. Instead the console says what would have been sent: "The form was sent: (no fields)". Everything you typed stayed on the page. There is a second sign too: a click on the word "Name" does not put the cursor in the field. On screen the text and the field sit next to each other, but nothing in the tree links them.
The idea
The form, the name and the label
A form (<form>) collects fields and sends them together. action says where to (/order), and method says how (post).
<form action="/order" method="post"> <label for="phone">Phone</label> <input id="phone" name="phone" type="tel"> <button type="submit">Place order</button></form>nameis what gets sent. On submit the browser makes a pairname=valuefor every field with a name:phone=0888123456. A field with nonameis not sent, however full it is. That is why the order was empty.<label>is the label of the field. It is linked withfor, which points to theidof the field. The field can also sit inside the label:<label>Phone <input name="phone"></label>. Thenforis not needed.
The label gives the field a name in the accessibility tree. A screen reader says "Phone, edit text", not just "edit text". A click on the label focuses the field, and a checkbox with a label can be ticked by its text. The checks in the tasks find the field by its label, the way a person does.
id and name are often the same, but they do different jobs. The id is for the label and for CSS, and the name is the key in the request.
The idea
The kinds of fields
<input> is one field, and type says what it is:
text(the default),email,tel: text. On a phoneemailandtelopen a keyboard with@or with digits.number: a number, with arrows.min="1",max="10"andstep="1"give the limits and the step.checkbox: a yes-or-no tick. It is sent only when ticked:gift=yesfromvalue="yes". Without avalueit sendsgift=on.radio: one choice out of several. Radio buttons form a group by a sharedname:name="delivery"on both, andvaluesays which one is chosen. Only the chosen one is sent.
Other fields:
<select>with<option>: a drop-down. It sends thevalueof the chosen option, or its text when there is novalue.<textarea>: text on several lines, like a note for the courier.<fieldset>with<legend>: a group of fields with a title. The delivery radio buttons are in afieldset, and thelegend"Delivery" is the name of the group. Without it a screen reader says "To an office", but not what the office is for.
The button. <button type="submit"> sends the form. <button type="button"> does nothing by itself, and its action is in JavaScript. Inside a form a <button> with no type is submit. So always write the type.
See it
The whole checkout
The checkout form with every kind of field. Fill it in, change the city and the delivery and press "Place order". Tick "Wrap as a gift" and press again.
Console
Look at the line in the console. The pairs are in the order of the fields in the code. The city is plovdiv: the value is sent, not the text "Plovdiv". From the delivery only the chosen button is there. The checkbox is missing until it is ticked. The empty note is sent as note=: an empty field with a name is still a field.
Try it
Fix the checkout
Fix the form from the start so that the order arrives whole. The texts "Name", "Phone" and "Address" become the labels of their fields, and the fields get the names name, phone and address.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Checkout · The shop</title>
</head>
<body>
<h1>Checkout</h1>
<form action="/order" method="post">
<label for="name">Name</label>
<input id="name" name="name">
<label for="phone">Phone</label>
<input id="phone" name="phone" type="tel">
<label for="address">Address</label>
<input id="address" name="address">
<button type="submit">Place order</button>
</form>
</body>
</html>
Every <p> becomes a <label for="…"> that points to the id of its field, and every field gets a name. The check finds the fields by their labels, types into them and reads the line in the console: name=Iva Petrova, phone=0888123456, address=7 Shipka St. If for points to an id that does not exist, the label is only text and the field is not found.
Try it
Find the bug
The buyer types a discount code and presses "Apply the code". The order is sent before they have typed their email. Which line is to blame?
Pick the line with the bug.
The <button> on line 3 has no type, and inside a form that means submit. A click on it sends the whole form. It should be <button type="button">, and the discount code is applied with JavaScript.
Try it
Fill in
Which attribute of the label points to the id of the field?
<label ="email">Email</label>
<input id="email" name="email" type="email">for: the label is "for" the field with this id. Without it (and without the field inside the label) the two are not linked.
The idea
The checks of the browser
An order with the email iva or with a quantity of 0 should not be sent. The browser can check a lot by itself, without a line of JavaScript. The rules are attributes of the field:
required: the field cannot be empty. For a checkbox: it must be ticked. For a radio group: something must be chosen.type="email": the text must look like an email (name@domain). An empty field passes if there is norequired.minandmax: the limits of a number.stepis the step: withstep="1", 2.5 does not pass.minlengthandmaxlength: how many characters at least and at most.pattern: a pattern for the whole text, like a regular expression.pattern="0[0-9]{9}"is a 0 and nine more digits.
When "Place order" is pressed, the browser checks every field. If one does not pass, the form is not sent and the submit event does not happen at all. The browser focuses the first invalid field and shows a message next to it in the language of the browser. The buyer fixes it, presses again, and the browser checks from the start.
novalidate on the <form> turns these checks off. It is used when the page checks and shows the errors with its own code. The lesson about forms in components is about that.
See it
See what happens on "Place order"
The code is a short checkout form. Watch the order: first the check, then the event, then the collecting.
<form action="/order" method="post"> <label>Name <input name="name" required></label> <label>Email <input name="email" type="email" required></label> <label>Phone <input name="phone" pattern="0[0-9]{9}"></label> <label>Quantity <input name="qty" type="number" min="1" max="10"></label> <label><input name="gift" type="checkbox"> Gift</label> <button>Place order</button></form>Step by step, in words
- The buyer presses "Place order". The <button> has no type, so inside a form it is a submit button. The browser does not send anything yet.
- First it checks every field in the order of the code against its rules. "iva" is not an email, and "12345" does not match the pattern.
- There are invalid fields, so the submit event does not happen. The browser focuses the first invalid field and shows a message only for it.
- The buyer fixes the email and the phone and presses again.
- The same check from the top. This time every field passes.
- Only now does the submit event happen. If the code does not stop it, the browser sends the form to action with method.
- It collects a name=value pair from every field with a name. The unchecked checkbox is not sent at all.
- With method="post" the pairs go in the body of the request, joined with &. The key is the name attribute, not the text of the label.
The browser checks all the fields, but shows a message only for the first invalid one. That is why a buyer sometimes fixes one field and right away sees a message for the next. And gift is not sent at all: for the server an unticked checkbox and a missing field are the same thing.
Try it
Add the checks
The quick order for the bread lets everything through. Add checks: the quantity is from 1 to 10, the email is required and must be an email, the phone is required and is a 0 and nine more digits. The check types 12, an empty email, "iva", an empty and a wrong phone, and expects the browser to stop on the right field.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Quantity · The shop</title>
</head>
<body>
<h1>Quick order: Dobrudzha bread, €1.80</h1>
<form action="/order" method="post">
<p><label>Quantity <input name="qty" type="number" value="1" min="1" max="10"></label></p>
<p><label>Email <input name="email" type="email" required></label></p>
<p><label>Phone <input name="phone" type="tel" required pattern="0[0-9]{9}"></label></p>
<button type="submit">Place order</button>
</form>
</body>
</html>
The quantity gets min="1" max="10", the email type="email" required, and the phone required pattern="0[0-9]{9}". The check presses "Place order" after every mistake and looks where the focus is: where the browser stopped. If a rule is missing, the form is sent and the focus is not on the field. In the end, with the right values, the console says qty=2, email=iva@example.com, phone=0888123456.
More precisely, if you are curious
The server checks again. The check in the browser is for the buyer's convenience: they see the mistake right away. It does not protect the shop. novalidate, the Elements panel or a request sent without a browser skip it, so the server checks every field again. The lesson "Untrusted input" explains why.
What is not sent. A field with no name, an unticked checkbox, an unchosen radio button and a field with disabled. A field with readonly is sent: it only cannot be changed. Files (type="file") need method="post" and enctype="multipart/form-data".
How it is sent. With method="get" (the default) the pairs go into the address: /order?name=…&phone=…. With method="post" they are in the body of the request. Either way they are encoded: @ becomes %40, and a space becomes +.
Enter. Enter in a text field sends the form like a click on the submit button, with the same checks.
minlength and a value from code. minlength checks only text that a person typed. A value from the value attribute or from JavaScript passes, even if it is short.
Without checks. formnovalidate on one button sends without checking (say, "Save draft"). form.submit() from JavaScript does not check either and does not fire the submit event, while form.requestSubmit() does everything a click does.
Styling the errors. CSS sees invalid fields with :invalid, and :user-invalid catches them only after a person has touched them. So a field is not red before the buyer has typed anything. The next module is about CSS.
autocomplete. autocomplete="email", "tel", "name", "street-address" tell the browser what to offer from what it remembers. The buyer fills in the checkout with two clicks.
Exercise
The checkout form
Make the checkout form from scratch, with the fields in this order. "Name" (name), required. "Email" (email), a required email. "Phone" (phone), required, a 0 and nine more digits. "City" (city): a drop-down Sofia, Plovdiv, Varna with the values sofia, plovdiv, varna. A group "Delivery" with two radio buttons (delivery): "To the address" (address), chosen at the start, and "To an office" (office). "Note for the courier" (note), on several lines. A checkbox "Wrap as a gift" (gift) with the value yes. A button "Place order".
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Checkout · The shop</title>
</head>
<body>
<h1>Checkout</h1>
<form action="/order" method="post">
<p><label>Name <input name="name" required></label></p>
<p><label>Email <input name="email" type="email" required></label></p>
<p><label>Phone <input name="phone" type="tel" required pattern="0[0-9]{9}"></label></p>
<p>
<label for="city">City</label>
<select id="city" name="city">
<option value="sofia">Sofia</option>
<option value="plovdiv">Plovdiv</option>
<option value="varna">Varna</option>
</select>
</p>
<fieldset>
<legend>Delivery</legend>
<label><input type="radio" name="delivery" value="address" checked> To the address</label>
<label><input type="radio" name="delivery" value="office"> To an office</label>
</fieldset>
<p><label>Note for the courier <textarea name="note"></textarea></label></p>
<p><label><input type="checkbox" name="gift" value="yes"> Wrap as a gift</label></p>
<button type="submit">Place order</button>
</form>
</body>
</html>
Every field is in its own <label>, so the check finds it by its text. The phone is type="tel" with required and pattern, and the empty name stops the first "Place order". The city is a <select> with a value on every option: otherwise "Plovdiv" is sent, not plovdiv. The radio buttons are in a <fieldset> with <legend>Delivery</legend>, share name="delivery", and checked picks the first one. The note is a <textarea>. The check fills in everything, chooses "To an office", ticks the gift and reads the line in the console.
Test
Test yourself
Related lessons
Section titled "Related lessons"- Builds on: The HTML document · Text, links, images, tables
- Continues in: Accessibility · Selectors and the cascade · Flexbox: row and column · The DOM: the page from JavaScript
- Tasks on this topic: HTML: the structure of a page