Chapter 7: Forms and User Interaction
Forms let people send information to your page. You can use them for small interactions that need no JavaScript or server. This chapter shows the essential elements and the built-in power of modern HTML forms. You will also learn small techniques that work locally such as mailto: and tel: links. These tools help you create useful pages that feel responsive and respectful to the reader.
Inputs, labels, and structure
Every form begins with a <form> element. Inside that element you place controls such as <input>, <label>, <textarea>, and <button>. Good structure makes a form clear and accessible. The most important rule is to associate each control with a label. You do this with a matching for and id.
<form action="" method="get">
<p>
<label for="name">Your name</label>
<input id="name" name="name" type="text">
</p>
<p>
<label for="email">Email address</label>
<input id="email" name="email" type="email">
</p>
<p>
<button type="submit">Send</button>
</p>
</form>
<label>. This automatically links them so you do not need for and id.
Group related controls with a <fieldset> and describe the group with a <legend>. This helps screen readers and makes long forms easier to read.
<form action="">
<fieldset>
<legend>Contact details</legend>
<label>Name <input name="name"></label>
<label>Email <input name="email" type="email"></label>
</fieldset>
</form>
Semantic order and keyboard flow
Place labels and inputs in a natural reading order. People often move through a form with the keyboard. The tab order follows the source order, so neat HTML becomes neat navigation.
Required attributes for clarity
Name each control with name. Without a name a control does not submit any value. Use unique id values to match labels and for CSS hooks where needed.
Textarea, buttons, autocomplete, and focus
Use <textarea> for longer passages of text that would feel cramped inside a single line. Browsers grow the control to show several lines at once so readers can see their thoughts gather in one place. A <textarea> can also hold placeholder hints, default values, and accessible labels. Treat it as a small writing space rather than a stretched input box and keep its purpose clear through a short label that sits close by.
Use <button> for actions such as submit, reset, or any custom task you define inside a form. A button carries meaning that a plain input does not and it adapts well to different devices. Labels inside a button are flexible so you can include short phrases, icons, or clear verbs such as Save or Search. This helps readers understand what will happen when they press it and gives you freedom to keep the form tidy.
Add the autocomplete attribute to help the browser suggest known values for names, addresses, phone numbers, and many other fields. These suggestions make forms friendlier and faster to complete, especially on mobile. Style focus states with care so that users can always see where the cursor is. Bright outlines or gentle highlights guide the eye during keyboard navigation and give the form a confident sense of place.
<form action="" method="get">
<p>
<label for="msg">Message</label>
<textarea id="msg" name="message" rows="5" cols="30" placeholder="Type your message…"></textarea>
</p>
<p>
<label for="city">City</label>
<input id="city" name="city" autocomplete="address-level2">
</p>
<p>
<button type="submit">Send message</button>
<button type="reset">Clear</button>
</p>
</form>
<style>/* minimal helpful focus styles */
:where(input, textarea, select, button):focus {
outline: 2px solid currentColor;
outline-offset: 2px;
}
</style>
Autofocus and helpful hints
Add autofocus to place the cursor in the first control. Add placeholder for short examples. Keep placeholders short since they are not a replacement for labels.
Useful input types
HTML provides many input types that validate and present friendly controls by default. These reduce errors and work without scripts.
<form action="" method="get">
<p>
<label for="e">Email</label>
<input id="e" name="email" type="email" required>
</p>
<p>
<label for="u">Website</label>
<input id="u" name="url" type="url" placeholder="https://…">
</p>
<p>
<label for="d">Date</label>
<input id="d" name="date" type="date">
</p>
<p>
<label for="r">Satisfaction</label>
<input id="r" name="satisfaction" type="range" min="0" max="10" step="1">
</p>
<p>
<label for="c">Favorite color</label>
<input id="c" name="color" type="color" value="#3366cc">
</p>
</form>
Numbers and phones
For digits use type="number" with min, max, and step. For phone numbers keep type="tel". This does not validate the format, however it opens a phone keypad on mobile which makes entry easier.
Validation and helpful attributes
Modern browsers validate many inputs automatically. You can guide this with attributes. This happens before a submit leaves the page, so it works with or without a server.
<form action="" method="get">
<p>
<label for="em">Email (required)</label>
<input id="em" name="email" type="email" required>
</p>
<p>
<label for="pw">Password (8 characters minimum)</label>
<input id="pw" name="password" type="password" minlength="8" required>
</p>
<p>
<label for="age">Age (18 to 120)</label>
<input id="age" name="age" type="number" min="18" max="120" step="1">
</p>
<p>
<label for="zip">Postal code (digits only)</label>
<input id="zip" name="postal" inputmode="numeric" pattern="\d{4,10}" placeholder="12345">
</p>
<p>
<button type="submit">Check</button>
</p>
</form>
title on a control to provide a friendly pattern hint. Browsers show this in their validation message.
Choosing get or post
When a form is submitted the browser has to package the values and send them somewhere. The method attribute tells the browser which format to use. In a typical site this information travels to a server that reads the data and replies with a new page. Even if you are not using a backend it is still useful to understand what these methods mean because they shape how the browser prepares the submission.
method="get" creates a URL that contains all the form values as key and value pairs. These appear in the address bar after a question mark, so the final link becomes a shareable request that anyone can paste or bookmark. This makes get suitable for searches, filters, and any small piece of public information. The format is a standard URL query so anything sensitive should not be sent this way.
method="post" hides the values inside the request body instead of appending them to the URL. The browser still sends the data to the destination but it does not appear in the address bar. This is better for longer messages, uploaded files, or private details. The submission is not a URL format so it cannot be bookmarked or revisited. When you work locally without a server the data does not go anywhere, however you still choose a method so the browser knows which structure to use when it prepares the action.
Creating a simple contact form
You can build a helpful contact page that works locally. The approach depends on your goal. The next examples show three patterns that do not need JavaScript or a custom server.
mailto: submit
A mailto: action asks the browser to open the reader’s email app with the form values. The values appear as message content. This is simple and can be enough for small personal sites.
<form action="mailto:your.name@example.com" method="post" enctype="text/plain">
<p>
<label for="mname">Your name</label>
<input id="mname" name="Name" required>
</p>
<p>
<label for="memail">Your email</label>
<input id="memail" name="Email" type="email" required>
</p>
<p>
<label for="mbody">Message</label>
<textarea id="mbody" name="Message" rows="6"></textarea>
</p>
<p>
<button type="submit">Email this to me</button>
</p>
</form>
mailto: depends on the user having a local email app. On some devices nothing happens, or values may be formatted differently. Treat this as a convenience rather than a guaranteed workflow.
tel: and phone-friendly links
On mobile a tel: link opens the dialer. You can place these links in or near a form so a reader can act at once without submitting anything.
<p>Prefer to call? <a href="tel:+441234567890">+44 1234 567 890</a></p>
You can also guide messaging apps. The sms: scheme opens the texting app with a number, and some platforms support a body parameter. Use this for quick replies or confirmations.
<p>Send a quick SMS: <a href="sms:+441234567890">+44 1234 567 890</a></p>
Download as a file
Sometimes the reader wants to keep what they typed. You can place the values into a data URL and offer a download link that contains the text. This needs no script when the content is fixed. When the content is dynamic you would normally use JavaScript. The next example shows a static pattern with a prepared file link.
<p><a download="contact.txt" href="data:text/plain,Name%3A%20…%0AEmail%3A%20…%0AMessage%3A%20…">Download example</a></p>
Search forms that target other pages
Another useful pattern is a form that redirects to an existing site and passes a search term. This needs no backend. You build a query string that matches the target site’s public search format.
<form action="https://www.example.com/search" method="get">
<p>
<label for="q">Search Example</label>
<input id="q" name="q" placeholder="Keywords">
<button type="submit">Search</button>
</p>
</form>
Datalist for friendly suggestions
<datalist> pairs with an <input> to offer short suggestions while the user types. The browser shows these suggestions in a small dropdown that feels similar to an autocomplete list, yet everything remains inside the page with no scripts involved. You control the list by placing <option> elements inside the datalist, each one representing a hint or a commonly chosen value.
This feature is helpful when you want to guide the user without forcing a fixed choice. The reader can pick a suggestion or ignore it and type something entirely new. That balance makes <datalist> ideal for topics, categories, tags, or any field where a gentle nudge is better than a strict menu. It also keeps the form light because the browser handles all the interaction on its own.
Since the list is local and stored in the HTML, it loads instantly and works the same online or offline. You can expand or trim the options whenever you like because each suggestion is a simple element. This gives your forms a friendly, guided feel while keeping the code clean and approachable.
<label for="topic">Topic</label>
<input id="topic" name="topic" list="topics">
<datalist id="topics">
<option value="Support">
<option value="Billing">
<option value="Feedback">
<option value="Other">
</datalist>
A complete no-backend contact block
This final example combines a simple form with mail, phone, and a direct email link. It gives readers several ways to reach you without scripts.
<section id="contact">
<h3>Contact me</h3>
<p>Use the form or choose a quick option below.</p>
<form action="mailto:your.name@example.com" method="post" enctype="text/plain">
<p>
<label for="cn">Your name</label>
<input id="cn" name="Name" required autocomplete="name">
</p>
<p>
<label for="ce">Your email</label>
<input id="ce" name="Email" type="email" required autocomplete="email">
</p>
<p>
<label for="ct">Topic</label>
<input id="ct" name="Topic" list="ctopics" placeholder="Choose or type…">
<datalist id="ctopics">
<option value="Support">
<option value="Quote">
<option value="Feedback">
</datalist>
</p>
<p>
<label for="cm">Message</label>
<textarea id="cm" name="Message" rows="6" placeholder="How can I help?"></textarea>
</p>
<p>
<button type="submit">Send by email app</button>
</p>
</form>
<p>Quick options: <a href="mailto:your.name@example.com">Email link</a> · <a href="tel:+441234567890">Call</a> · <a href="sms:+441234567890">Text</a></p>
</section>
You now have practical tools that work anywhere. With careful labels, sensible types, and built-in validation you can build forms that respect the reader. Local patterns such as mailto: and tel: add simple paths for action while you keep your pages light and fast.