From lesson 2-1, in <a href="/apply">Apply</a>, the href="/apply" part is an attribute, a name="value" setting written inside the opening tag.
| Piece | Name |
|---|---|
href | the attribute name |
/apply | the attribute value |
Forms are built almost entirely out of attributes, so this word is worth keeping close at hand for the rest of the unit.
Forms collect input
Everything built so far only shows content. A form collects it, and collecting input is where the web earns money and gets work done, since every login, search box, checkout, and signup on the internet is a form.
It is also where accessibility and validation bugs cluster, which is why interviewers like asking about the details below.
<form action="/signup" method="post"> <label for="email">Email</label> <input id="email" name="email" type="email"> <button type="submit">Sign up</button> </form>
| Element or attribute | Role |
|---|---|
form | wraps everything |
action | the URL the data goes to |
method="post" | send data rather than just fetch a page |
input | a void element rendering the field |
name | the key the server receives the value under |
label | names the field for humans |
for | must equal the input's id |
button type="submit" | submits the form |
Pairing the label to the input through for and id, where id is the unique element name from lesson 2-3, is what makes clicking the label focus the input and lets screen readers announce the field properly.
Every input gets a label. An unlabelled field is unusable with assistive technology, and it is the single most common accessibility defect in real forms.
Input types
One element covers many behaviors, because the type attribute changes both the widget and the built-in checks.
| type | What you get |
|---|---|
text | a plain one-line text box |
email | a text box, and submits without a valid-looking email are blocked |
password | characters shown as dots |
number | a numeric keyboard on phones, respecting min and max |
checkbox | an on or off box |
radio | pick one of a group, given one shared name |
Two more form elements round out the set. textarea handles multi-line text, and select with option children builds a dropdown.
Adding the required attribute to any field makes the browser refuse to submit while it is empty. That is free validation before a single line of JavaScript exists, and lesson 9-3 covers the JavaScript version for custom rules.
What a label pairs with
A label's for attribute must match the input's id.
| Attribute | Pairs with | Purpose |
|---|---|---|
for on the label | id on the input | links them in the browser |
name on the input | nothing on the label | the key the server sees |
The name attribute is unrelated to labels, existing only so the submitted data arrives under a known key. Confusing the two produces a form that submits correctly while remaining unusable with a screen reader, which is why the pairing is worth checking deliberately.
A signup form wired up properly
One field, with the label, the id, and the browser's own validation all in place.
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Signup</title> </head> <body> <h1>Join the study group</h1> <form> <label for="email">Email</label> <input id="email" name="email" type="email" required> <button type="submit">Sign up</button> </form> </body> </html>
| Detail | Effect |
|---|---|
for="email" matching id="email" | clicking the label focuses the field |
type="email" | a malformed address is rejected |
required | an empty field is rejected |
The label comes first, which matches reading order for both sighted and screen reader users. required needs no value, since its presence alone is the setting.
Submitting the form while empty demonstrates both checks at once, with the browser blocking the submit and pointing at the field.
Writing your own email check
The browser's type="email" check is a black box, so a simple version makes the rules explicit. looksLikeEmail(value) is true only for exactly one @, at least one character before it, and a dot somewhere after it.
function looksLikeEmail(value) { const at = value.indexOf("@"); if (at < 1) return false; if (value.indexOf("@", at + 1) !== -1) return false; return value.indexOf(".", at + 1) !== -1; } console.log(looksLikeEmail("ada@example.com")); console.log(looksLikeEmail("ada@examplecom")); console.log(looksLikeEmail("@example.com")); console.log(looksLikeEmail("ada@ex@ample.com"));
Output
true false false false
| Input | Fails on |
|---|---|
ada@example.com | nothing, it passes |
ada@examplecom | no dot after the @ |
@example.com | nothing before the @ |
ada@ex@ample.com | a second @ |
indexOf("@") returns the position of the first @, or -1 when there is none, and both 0 and -1 fail the at-least-one-character rule in a single comparison.
Passing a start position, as in indexOf("@", at + 1), searches for a second @ after the first, and finding one rejects the value. The final rule is one line, and the whole function shows why real email validation is famously harder than it looks.