Chapter 5 — Forms and User Input
Section 1 — The form element
A form is a section of a page where a visitor enters information and sends it somewhere, and the form element is the container that makes that possible. Without a form element, a page can still contain text fields and buttons, but they will not work together as a unit: there will be no defined set of fields to collect, no destination to send them to, and no submit action that gathers them all at once. The form element provides that unity. It wraps the fields that belong together, it declares where their data should go and how it should be sent, and it gives the browser a submit behaviour — pressing a submit button, or pressing Enter in a text field, collects the form's data and sends it. That behaviour is built in, which means a basic form works with no JavaScript at all, and that is a strength worth appreciating before adding any scripting. A form that works without JavaScript is a form that works for every visitor, on every device, even when scripts fail.
A form's job is to turn a visitor's input into a structured message. Each field in the form contributes a piece of that message, identified by its name, and the browser assembles those pieces when the form is submitted. That is why a form is more than a visual grouping: it is a data structure, and its fields are its parts. A contact form, for example, is not merely a name field, an email field, and a message box arranged on a page; it is a single unit that says "these three pieces of information belong together, they are about one message, and they should be sent together to one destination". If those fields were not wrapped in a form, they would be three unrelated inputs, and the visitor would have no way to send them as a message, however well they were styled. The form element is what turns inputs into a message, and that transformation is its essential purpose.
Forms appear everywhere on the web, often without being recognised as forms: a search box is a form, a login box is a form, a checkout is a form, a comment box is a form, and a newsletter signup is a form. Recognising that variety matters, because it means form skills apply far beyond the obvious "contact us" page. Every time a visitor types something and a site acts on it, a form is involved, and the quality of that form — whether it is clear, accessible, and forgiving of mistakes — determines whether the visitor succeeds or gives up. A form is therefore not a peripheral part of front-end development; it is one of its central interactions, the point where a page stops being something a visitor reads and becomes something a visitor uses. Learning forms well, from their structure to their validation to their accessibility, is learning how the web's two-way conversation actually works.
<form>
<label>Name <input name="name"></label>
<button>Send</button>
</form>
Check: What does wrapping fields in a form element give them that loose fields lack?
Section 2 — Action and method
Two attributes on the form element control what happens when it is submitted: action and method. The action attribute says where the form's data should be sent — the address of the server endpoint, page, or script that will receive it. The method attribute says how it should be sent, using one of the HTTP methods, of which the two relevant to forms are GET and POST, covered in detail in the next section. Together, they are the form's delivery instructions: action is the destination, method is the manner of delivery, and without them, the browser falls back to defaults that may not match the author's intent. A form without an action submits to the page's own address, which is sometimes intended — for a search form on a results page, for example — but more often it is an oversight, and the data goes nowhere useful, or to a page that does not know what to do with it. A form without a method uses GET, which is sometimes correct and sometimes not, as the next section explains.
Writing action and method explicitly, even when the defaults would do, is good practice, because it makes the form's intent visible in the code. A future maintainer reading <form action="/contact" method="post"> knows immediately where the data goes and how, without having to remember the defaults or guess whether their absence was deliberate. That clarity matters particularly for action, because the destination is often the most important fact about a form: it determines which server code runs, which database is updated, and which person receives the message. An action that points to a relative path, like "/contact", is resolved against the site, like any relative link, and it keeps working if the site moves domain, which is one more reason to prefer relative paths for a site's own endpoints. An action that points to another site's endpoint is possible, but it should be deliberate, because it sends the visitor's data to a third party, and that has privacy implications the visitor may not expect.
There is a modern nuance worth noting. Many forms today are handled by JavaScript, which intercepts the submit event, reads the form's data, and sends it with a fetch request, rather than letting the browser perform its default submission. In those cases, action and method may seem redundant, because JavaScript decides where the data goes. But they are still worth writing, for two reasons. First, they provide a fallback: if JavaScript fails, or is disabled, the browser's default submission still works, sending the data to the action, by the method, in the traditional way, rather than doing nothing. Second, they document the form's intent, for humans and for tools, in a standard place, rather than hiding it inside script code. A form with a correct action and method, enhanced by JavaScript, is a form that works twice over: with scripts, and without them. A form that relies entirely on JavaScript, with no action, works only once over, and fails silently when its one mechanism fails. The attributes are small, but they are the form's safety net, and safety nets should be in place before they are needed, not after.
<form action="/contact" method="post">
<!-- fields go here -->
</form>
Check: What do the action and method attributes each control?
Section 3 — GET versus POST
GET and POST are the two HTTP methods used by forms, and choosing between them is a decision about what the form's submission means. GET appends the form's data to the address, as a query string: a search for "bread" on a site becomes an address like /search?q=bread, visible in the address bar, bookmarkable, shareable, and stored in the browser's history. That visibility is GET's strength for safe, repeatable requests: a search, a filter, a sort order — any request that merely asks for information, without changing anything on the server. Because GET requests are meant to be safe, browsers and servers may repeat them without asking — refreshing a page, following a bookmark, or preloading a link — and that repetition is harmless for a search, because searching twice changes nothing. GET is therefore the correct method for forms that retrieve, filter, or search, and its bookmarkability is a feature: a visitor can save a search, share it with someone else, and return to it later, because the search itself is encoded in the address.
POST sends the form's data in the body of the request, not in the address, so it is not visible in the address bar, not bookmarkable, and not stored in history in the same way. That invisibility is appropriate for requests that change something: creating an account, posting a comment, placing an order, sending a message, or updating a record. Those actions should not be repeatable by accident, because repeating them would create a second account, a duplicate comment, or a second order, and browsers understand that: they warn before resubmitting a POST request, for example when refreshing a page that resulted from one, precisely because resubmission might repeat a change. POST is therefore the correct method for forms that create, update, or send, and its non-bookmarkability is a feature, not a flaw: a message sent is not a page to return to, and it should not be bookmarked as if it were. The distinction is often summarised as "GET reads, POST writes", and while real systems are more nuanced, that summary is a reliable starting point for form design.
There are practical consequences beyond the summary. GET requests have limits on how much data they can carry, because addresses have practical length limits, and a form with a long message, or a file upload, cannot use GET — a file upload requires POST, with a special encoding covered later in this chapter. GET data is also visible to anyone who sees the address, including in server logs and in shared links, so sensitive information — passwords, personal details, private messages — must never be sent by GET, even if the form is "only" a login or a contact form, because the data would appear in the address bar, in history, and in any link the visitor shares. POST data is not visible in the address in the same way, though it is still not encrypted by itself — encryption comes from HTTPS, which should be used for all forms, regardless of method.
<form action="/search" method="get">
<label>Search <input name="q"></label>
<button>Search</button>
</form>
Check: Why should a form that sends a message not use GET?
Section 4 — Labels
A label element associates a piece of text with a form field, and that association is one of the most important accessibility features in HTML. A label does two things at once: it gives the field a visible name, so that a sighted visitor knows what to enter, and it gives the field a programmatic name, so that a screen reader can announce what the field is for when the visitor reaches it. Without a label, a screen reader user arriving at a text field hears only "edit box" or "text field", with no indication of whether it wants a name, an email, or a password, and they must guess from context, or explore the surrounding text, to find out. With a label, they hear "Name, edit box" or "Email, edit box", and they know immediately. That difference — between a field with a name and a field without one — is the difference between a form that is usable and a form that is a guessing game, and it costs the author a single element.
There are two ways to associate a label with a field, and both are valid. The first is to wrap the field inside the label element, so that the label's text and the field are together in the markup. The second is to give the field an id, and to give the label a for attribute whose value matches that id, so that the label points at the field even when they are not adjacent in the markup. The second form is generally preferred, because it separates the label from the field's position, which gives CSS more freedom to lay them out — label above the field, beside it, or elsewhere — without breaking the association, and because it works for fields that cannot be wrapped, such as a field whose label and input are in different parts of a complex layout. Whichever form is used, the association must be exact: a for attribute that does not match any id, or an id that is duplicated, breaks the association silently, leaving the field unnamed for assistive technology, even though it looks labelled on screen. Testing a label means testing the association, not just the appearance, by clicking the label text and checking that the field receives focus — because a correctly associated label, when clicked, focuses its field, which is a useful benefit for all visitors, particularly on small screens where fields are hard to tap precisely.
Labels are not the same as placeholders, a distinction covered later in this chapter, and they are not optional decorations to be omitted for a cleaner design. A form that relies on placeholder text alone, with no labels, is a form whose fields lose their names the moment the visitor starts typing, and whose fields have no names at all for screen reader users who navigate by labels.
<label for="email">Email address</label>
<input id="email" name="email" type="email">
Check: What happens when a visitor clicks a correctly associated label?
Section 5 — Text inputs
The input element is the workhorse of forms, and its type attribute determines what kind of input it is. The default type, text, creates a single-line text field, suitable for short, free-form answers: a name, a city, a subject line, or any other piece of text that fits on one line and has no special format. A text input is the most general field, and its generality is both its strength and its weakness: it accepts anything, which makes it flexible, but it also validates nothing, which means the author must add validation — required, pattern, maxlength, or JavaScript checks — if the field's content must follow a format. A text input without any constraints is appropriate when the answer genuinely has no format, such as a person's name, which can contain letters, spaces, hyphens, apostrophes, and characters from many writing systems, and which resists any simple pattern. Forcing a name into a restrictive pattern is a common form-design error, and it excludes real people whose names do not fit the author's assumptions.
The attributes that commonly accompany a text input each serve a distinct purpose. The name attribute identifies the field's data when the form is submitted, and it is required for the field to be included at all — a field without a name is ignored on submission, however well it is labelled and styled. The id attribute, distinct from name, is used for labelling and for scripting, and it must be unique on the page. The maxlength attribute limits how many characters the visitor can enter, which is useful for fields with a genuine limit, such as a subject line or a code, but it should not be used to truncate answers that legitimately need more room. The size attribute suggests how wide the field should be, in characters, though CSS is the better tool for controlling width, and size is now largely a fallback. The value attribute sets the field's initial content, which is useful for editing an existing value — a profile form pre-filled with the user's current name, for example — but it should not be used to show a hint, because a pre-filled value looks like an answer, and a visitor may submit it unchanged, thinking it is a placeholder, which is a confusion the placeholder attribute exists to avoid.
Text inputs also have behaviours worth knowing. Pressing Enter in a single-line text input, when the form has a submit button, submits the form, which is convenient for search boxes and login forms, but can surprise visitors in longer forms, where Enter in an early field submits before they have finished. That behaviour is standard, and it should be anticipated in form design, by ensuring that a form's early fields do not trap a visitor who presses Enter out of habit. Text inputs are also where autocomplete, spellchecking, and input modes come into play, covered later in this chapter, and those features all build on the basic text input, refining it for specific kinds of text without changing its fundamental nature.
<label for="name">Name</label>
<input id="name" name="name" type="text">
Check: Why must a field have a name attribute to be submitted?
Section 6 — Email, telephone, and other specific types
The input element's type attribute offers specialised types for common kinds of data, and using them instead of plain text brings three benefits at once: validation, keyboard, and meaning. The email type, for example, tells the browser that the field should contain an email address, and the browser will check, on submission, that the value looks like one — that it contains an at sign, and a plausible domain part — and will refuse to submit, with a message, if it does not. That check is basic, not perfect: it does not verify that the address exists, or that it belongs to the visitor, but it catches the most common errors, such as a missing at sign or a forgotten domain, without any JavaScript. The tel type, for telephone numbers, does not validate by default, because phone formats vary too widely around the world for a single pattern to be correct, but it does tell mobile devices to show a telephone keypad, with numbers and symbols, instead of a full keyboard, which makes entering a number much easier on a phone. The url type validates that the value looks like a web address, and shows a keyboard with shortcuts for dots and slashes. The password type hides the entered characters, for privacy, and the search type marks the field as a search box, which some browsers style distinctively and which assistive technology announces as a search field. Each type is a small piece of meaning, and together they make forms smarter, with no extra code.
The choice among these types is a matter of matching the type to the data, and the most common error is to use text for everything, out of habit or caution, and thereby lose all three benefits. A field that asks for an email address but uses type="text" will accept "not an email" without complaint, will show a general keyboard on a phone, and will be announced to a screen reader as a generic text field, rather than as an email field, which is less informative. A field that asks for a telephone number but uses type="number" is a different error, and a worse one: number is for quantities, not for phone numbers, which are identifiers that happen to contain digits, and using number for a phone number breaks numbers that start with zero, contain plus signs, or contain spaces and hyphens, all of which are valid in phone numbers but invalid in a numeric field. The rule is to think about what the data is, not what it looks like: an email address is an address, a phone number is an identifier, a quantity is a number, and each has its own type, or, in the phone case, its own type with no built-in validation, precisely because its format is too varied to validate simply.
Specific types also interact with autocomplete, which browsers use to offer to fill a field from the visitor's saved information. A field marked as email, with an appropriate autocomplete value, can be filled with one tap, rather than typed from scratch.
<label for="email">Email address</label>
<input id="email" name="email" type="email">
<label for="phone">Phone number</label>
<input id="phone" name="phone" type="tel">
Check: Why is type="number" wrong for a phone number?
Section 7 — Textareas for longer text
A textarea element creates a multi-line text field, for answers that need more than one line: a message, a comment, a description, or any other free-form text that may run to several sentences or paragraphs. It is a separate element from input, not a type of input, because its behaviour is different in kind, not just in size: it allows line breaks, it can be resized by the visitor in most browsers, and it displays several lines at once, so that the visitor can see and edit a longer answer without scrolling within a single line. The rows and cols attributes suggest its initial size — rows for how many lines tall, cols for how many characters wide — though CSS is the better tool for controlling its displayed size, and rows and cols are now mainly fallbacks for browsers without CSS, and hints for the initial rendering. The textarea's content, unlike an input's value, is written between its opening and closing tags, not in a value attribute, which is a distinction beginners often miss: a textarea with a value attribute will ignore it, and a textarea whose initial text is placed in a value attribute will appear empty.
Textareas raise design questions that single-line inputs do not. How large should the field be initially? Too small, and the visitor must scroll within the field to review what they have written, which is frustrating for a long message. Too large, and the form looks intimidating, and the page is dominated by an empty box that suggests a long answer is expected, even when a short one would do. The answer depends on the expected answer: a comment box might start at four or five rows, a message box at six or eight, and a code or description field larger still, with resizing enabled so that the visitor can enlarge it if they need more room. The maxlength attribute can limit a textarea's content, which is useful when a system genuinely has a limit — a comment system that stores at most a certain number of characters, for example — but it should be communicated to the visitor, with a character count or a note, because a limit that silently truncates an answer, or refuses submission without explanation, feels arbitrary. A textarea should also have a label, like every field, and that label should say what kind of answer is wanted, not merely "Message" when the message has a specific purpose, such as "Tell us about the problem, including what you were doing when it happened".
There is also a practical point about line breaks in textarea content. When a visitor types line breaks in a textarea, those breaks are part of the submitted data, and the server or script that receives it must handle them — by preserving them, by converting them to paragraphs, or by storing them as written, depending on the system.
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" cols="40"></textarea>
Check: Where does a textarea's initial text go — in a value attribute or between its tags?
Section 8 — Select menus and options
A select element creates a dropdown menu, from which the visitor chooses one option — or, with the multiple attribute, several — from a list the author provides. Each option is an option element, with a value attribute that determines what is submitted, and text content that is what the visitor sees. That separation, between value and visible text, is useful: the visible text can be human-friendly, like "Nigeria", while the value can be a code, like "NG", that the receiving system prefers, and the visitor never needs to know the code exists. The select element's name attribute, like any field's, identifies its data on submission, and its id associates it with a label, which should say what the visitor is choosing — "Country", "Size", "Payment method" — not merely "Select", which tells them nothing about the choice. A select menu is appropriate when the choices are known in advance, limited in number, and familiar to the visitor, such as a country list, a size range, or a set of categories. It is not appropriate when the choices are numerous and unfamiliar, or when the visitor may need a choice that is not listed, in which case a text field, or a text field with suggestions, may serve better.
Options can be grouped with optgroup, which creates a labelled group within the menu — for example, grouping countries by region, or products by category. The optgroup's label attribute names the group, and browsers render the group label as a non-selectable heading within the menu, which helps the visitor navigate a long list without adding selectable items that are not real choices. That grouping is a structural feature, not merely a visual one: screen readers announce the group as the visitor moves through its options, giving context that a flat list would lack. An option can also be marked as selected, to pre-select it, and as disabled, to show it but prevent its selection — useful for a placeholder option like "Choose a country", which should be displayed initially but should not be submittable as an answer, though that pattern must be combined with validation, because a disabled option cannot be selected, and a selected placeholder that is not disabled can be submitted as if it were a real choice, which is a common form bug. The cleanest placeholder pattern is an option with an empty value, marked as selected and disabled, with text like "Choose a country", combined with a required attribute on the select, so that the browser refuses submission until a real option is chosen.
Select menus have limitations that should influence when they are used. They are difficult to style consistently across browsers, because much of the dropdown's appearance is controlled by the operating system, not by CSS, and attempts to restyle them heavily often produce inconsistent results. They are also cumbersome for long lists on mobile devices, where the browser replaces the dropdown with a native picker, which is actually a benefit, because the native picker is familiar to the device's user, but it means the author has less control over the experience than they might expect.
<label for="country">Country</label>
<select id="country" name="country">
<option value="">Choose a country</option>
<option value="NG">Nigeria</option>
<option value="GH">Ghana</option>
</select>
Check: What is the difference between an option's value and its visible text?
Section 9 — Radio buttons
Radio buttons are a set of options from which the visitor chooses exactly one. Each option is an input of type radio, with the same name attribute for all options in the set — that shared name is what groups them, and what makes them mutually exclusive: selecting one deselects the others, because the browser treats all inputs with the same name as a single field with several possible values. Each radio button has its own value attribute, which is what is submitted when that option is chosen, and each should have its own label, so that the visitor can click the label text, not just the small circle, to select it, which is particularly helpful on touch screens, where the circle itself is a small target. The group as a whole should also have a label, in the form of a fieldset with a legend, covered later in this chapter, which names the question the group answers — "Delivery method", for example — so that a screen reader user hears the question before hearing the options, rather than hearing a series of options with no context for what they are choosing between.
Radio buttons are appropriate when the options are few, mutually exclusive, and worth seeing all at once. A choice between three delivery methods, or between four subscription plans, or between yes and no, is a natural radio-button group, because the visitor benefits from comparing the options side by side, and because the number of options is small enough that displaying them all does not overwhelm the page. They are not appropriate for long lists, where a select menu is more compact, or for choices that are not mutually exclusive, where checkboxes, covered next, are correct. A common error is to use radio buttons for a yes-or-no question with a single radio button, which is nonsensical, because a single radio button cannot be deselected once selected, and it offers no way to answer "no" — a yes-or-no question needs two radio buttons, one for each answer, or a single checkbox, depending on whether the question is framed as a choice or as an agreement. Another common error is to give each radio button a different name, which breaks the grouping, so that all options can be selected at once, defeating the purpose of radio buttons entirely. The shared name is not a detail; it is the mechanism.
There is also a question of pre-selection. A radio group can have one option pre-selected, with the checked attribute, which is appropriate when there is a sensible default — the most common delivery method, for example — and when pre-selecting it does not bias the visitor toward a choice they would not have made. Pre-selecting the first option by default, without thinking, is a bias, particularly for questions where the visitor has no default preference, because many visitors will accept the pre-selected option without considering the others. For questions where there is no sensible default, it is better to pre-select none, and to make the group required, so that the visitor must make an active choice, rather than submitting a default they never considered.
<fieldset>
<legend>Delivery method</legend>
<label><input type="radio" name="delivery" value="standard"> Standard</label>
<label><input type="radio" name="delivery" value="express"> Express</label>
</fieldset>
Check: What groups radio buttons together so only one can be chosen?
Section 10 — Checkboxes
A checkbox is an option that can be switched on or off independently of other checkboxes. Each checkbox is an input of type checkbox, and unlike radio buttons, checkboxes with the same name do not exclude each other: a visitor can check any combination, including none, or all. That independence makes checkboxes appropriate for questions where multiple answers are possible, or where a single agreement is being sought. A list of interests, where a visitor may select several, is a natural checkbox group, with each checkbox having the same name and a different value, so that all selected values are submitted together. A single agreement, such as "I agree to the terms", is a natural single checkbox, with its own name, whose presence in the submitted data means agreement, and whose absence means the visitor did not agree — which is why a required checkbox is a meaningful validation: the form cannot be submitted until the visitor has checked it, which is the point of an agreement.
Checkboxes have a subtlety in how their data is submitted, which authors must understand to avoid bugs. A checkbox that is checked submits its name and value; a checkbox that is unchecked submits nothing at all — not a value of "false" or "no", but no entry whatsoever for that field. That means a receiving system cannot distinguish, from the submitted data alone, between a checkbox that was present and unchecked, and a checkbox that was not present at all, unless it knows the form's structure in advance. For a single agreement checkbox, that distinction rarely matters, because the system knows the checkbox exists, and its absence means "not agreed". For a group, it matters more: a group where no boxes are checked submits no data for that group, and the system must interpret that as "none selected", not as "group missing". Authors who need an explicit "false" value for an unchecked box must handle that in their receiving code, or use a hidden input with the same name and a default value, a technique that works because the hidden input's value is submitted when the checkbox is unchecked, and is overridden — or accompanied, depending on the system — when it is checked, which requires care to implement correctly.
Labels are particularly important for checkboxes, because the clickable area matters. A checkbox itself is a small square, and on a touch screen, tapping it precisely can be difficult. A correctly associated label, which focuses or toggles the checkbox when clicked, effectively enlarges the target to include the label text, making the checkbox much easier to use, for every visitor, and particularly for visitors with motor difficulties. The label text should also be clear about what checking the box means, in plain language, without legal jargon where possible: "Send me the newsletter" is clear, while "I consent to receive marketing communications" is accurate but less clear, and the clearer wording is preferable wherever the legal requirements allow it. A checkbox group, like a radio group, should be wrapped in a fieldset with a legend naming the question, so that screen reader users hear the question before the options.
<label><input type="checkbox" name="newsletter" value="yes"> Send me the newsletter</label>
Check: What does an unchecked checkbox submit?
Section 11 — Submit buttons
A submit button is the control that sends a form. In its simplest form, it is an input of type submit, or a button element with type submit, whose text — its value, or its content — says what will happen when it is pressed, such as "Send message", "Create account", or "Search". When pressed, it triggers the browser's form submission process: it validates the form's fields, according to their required, pattern, and type constraints, and if validation passes, it collects the fields' data and sends it to the form's action, by its method. If validation fails, it does not submit, and the browser shows messages for the fields that failed, focusing the first one, so that the visitor can correct it. That built-in process is why a submit button is more than a styled rectangle: it is the trigger for a whole sequence — validation, collection, submission — that the browser performs automatically, without any JavaScript, and that sequence is the form's core behaviour.
The button's text deserves care, because it is the visitor's last decision point. A button that says "Submit" tells the visitor nothing about what will happen — submit what, to whom, with what consequence? A button that says "Send message" tells them exactly: pressing it will send the message they have written. A button that says "Create account" tells them they are about to create an account, with all that implies. That specificity matters particularly for consequential actions — payments, account creation, message sending — where the visitor should be certain about what pressing the button will do before they press it. It also matters for accessibility: a screen reader user navigating by buttons will hear the button's text, and "Send message" is informative in a list of buttons, while "Submit" is not, because every form's button would say the same thing. The button text should be a verb phrase describing the action, in the visitor's language, without jargon, and it should match the action the form actually performs, so that the promise it makes is kept when it is pressed.
A form can have more than one submit button, and when it does, each button can carry a name and value, so that the receiving system knows which button was pressed. That is useful for forms with multiple outcomes — "Save draft" versus "Publish", for example, where the same fields are submitted, but the action taken depends on which button was pressed. In that case, the button that was pressed contributes its name and value to the submitted data, while the other buttons contribute nothing, which lets the server distinguish the outcomes. A submit button can also carry a formaction or formmethod attribute, which overrides the form's action or method for that button alone, allowing one button to send the form to a different destination, or by a different method, than the form's default.
<button type="submit">Send message</button>
Check: What does a submit button trigger besides sending the data?
Section 12 — Button types
The button element can act as three different kinds of control, depending on its type attribute, and confusing those types is a common source of form bugs. A button with type="submit" submits its form, as described in the previous section, triggering validation and sending the data. A button with type="reset" resets its form, clearing all fields to their initial values, which is a destructive action that should be used rarely, and with caution, as covered later in this chapter. A button with type="button" does nothing at all by itself: it is a generic button, intended to be given behaviour by JavaScript, such as opening a dialog, adding a field, or toggling a section, without submitting or resetting anything. That three-way distinction is essential, because a button's default type, when no type is given, is submit, when the button is inside a form — which means a button intended as a generic JavaScript control, placed inside a form without a type, will unexpectedly submit the form when pressed, which is one of the most common and confusing form bugs. The remedy is simple and absolute: always give a button element an explicit type, even when the type is submit, so that its behaviour is declared, not assumed.
The input element also offers button-like types, and the distinction between input buttons and button elements is worth understanding. An input of type submit or button is a button whose label is its value attribute, which means it can contain only plain text, not HTML — no icons, no styled spans, no line breaks. A button element, by contrast, can contain HTML content — text, images, icons, and other inline elements — which makes it more flexible for design, and it is the modern preference for most buttons, for that reason. An input of type image is a special case: it is an image that acts as a submit button, submitting the form when clicked, and also submitting the coordinates of the click, which was historically used for image maps, but is now rarely needed, and should generally be replaced by a button element containing an image, which is clearer and more accessible. For new forms, the guideline is to use button elements, with explicit types, for all buttons, and to reserve input buttons for the rare cases where an input is genuinely required, such as in a legacy system that expects them.
Choosing a button's type is therefore a decision about its behaviour, not its appearance. A button that looks like a submit button but has type="button" will not submit, however submit-like its styling may be, and a button that looks like an ordinary button but has no type, inside a form, will submit, however ordinary it looks. Appearance and behaviour are separate, in buttons as in all of HTML, and the type attribute is where behaviour is declared.
<button type="submit">Send</button>
<button type="button">Add another</button>
Check: What does a button inside a form do if it has no type attribute?
Section 13 — The name attribute
The name attribute is the field's identity in the submitted data. When a form is submitted, the browser collects each field's name and its current value, and sends them as pairs: name=value, for every field that has a name and is not disabled. A field without a name is not included at all, however visible, labelled, or well-filled it may be, because the browser has no key under which to send its value. That rule is absolute, and it is the single most common reason a form "loses" a field's data: the field was present, the visitor filled it in, but it had no name, so its value never left the browser. The name is therefore not a decorative identifier, like a class, or a scripting hook, like an id, though it can serve related purposes in JavaScript. It is the field's name in the message the form sends, and it should be chosen as carefully as any other part of that message, because the receiving system will use it to find the field's value, and a name that is unclear, inconsistent, or duplicated in the wrong way will make that system harder to write and harder to debug.
Names should be meaningful, consistent, and stable. Meaningful means the name describes the field's data, in a way the receiving code can understand without a dictionary: "email" for an email field, "first_name" for a first name, "message" for a message, rather than "field1", "x", or "input3", which tell the receiving developer nothing and which become wrong the moment fields are reordered. Consistent means following one convention throughout a form and across a site: if one field uses "first_name", another should not use "firstName" or "first-name" for the same kind of data, because inconsistency forces the receiving code to handle multiple conventions for the same concept, which is a source of bugs. Stable means a name, once chosen and used in a live form, should not be changed casually, because the receiving system, the database, and any integrations that depend on the name will all break if it changes, and changing a name is therefore a coordinated change, not a cosmetic one. Those three qualities — meaningful, consistent, stable — apply to names in code generally, but they matter most for form names, because form names cross a boundary, from the page to the server, and a boundary is where misunderstandings are most costly.
There is a distinction between name and id that beginners often blur, and it is worth stating clearly. The id identifies an element on the page, for labelling and scripting, and it must be unique. The name identifies a field's data in the submission, and it need not be unique: radio buttons share a name deliberately, to form a group, and checkboxes in a group share a name deliberately, to submit multiple values together. An element can have an id without a name — in which case it is identifiable on the page but not submitted — and a name without an id — in which case it is submitted but cannot be labelled by for, though it can still be labelled by wrapping.
<label for="email">Email</label>
<input id="email" name="email" type="email">
Check: What happens to a field's value if the field has no name?
Section 14 — The value attribute
The value attribute sets a field's initial value, or, for checkboxes, radio buttons, and options, the value that is submitted when that choice is selected. Those two roles — initial content, and submitted value for a choice — are related but distinct, and confusing them leads to errors. For a text input, value sets what the field contains when the page loads: a profile form editing an existing name might set value to the user's current name, so that the visitor can edit it rather than retyping it. That initial value is not a hint, and it is not a placeholder: it is a real value, and if the visitor submits the form without changing it, that value is submitted as their answer, which is correct for an editing form, where the current value is the default answer, but incorrect for a new form, where a pre-filled value might be mistaken for a hint and submitted unchanged by a visitor who did not notice it. For a checkbox or radio button, value sets what is submitted when that option is chosen, and it is essential: a checkbox without a value submits the default value "on" when checked, which is uninformative, while a checkbox with value="yes" submits a meaningful "yes", which the receiving system can interpret.
The value attribute interacts with the visitor's input in a specific way: once the page has loaded, the field's current value is what the visitor has typed or selected, not what the value attribute says. The attribute sets the starting point; the visitor's actions move it from there. JavaScript can read the current value through the field's value property, and can set it by assigning to that property, and those operations affect the current value, not the attribute, though the attribute can be read separately as the default value, which is useful for resetting a form to its initial state. That distinction — between the attribute as the initial value, and the property as the current value — is a JavaScript detail, but it reflects a general principle: HTML describes the initial state, and the visitor's interaction, and JavaScript, change the current state from there. A form author who understands that principle will not be surprised when changing a value attribute in the source does not change a field the visitor has already edited, because the field's current value has diverged from its initial value, and the browser correctly preserves the visitor's input over the author's initial setting.
There is also a security and correctness point about values. A value attribute is visible in the page's source, and it is sent to every visitor, so it must never contain sensitive information — a password, a token, a private identifier — that the visitor should not see, even if the field itself is hidden, because hidden fields are hidden from display, not from the source. And a value that comes from user input, or from a database, and is inserted into a value attribute by a template, must be escaped, so that a value containing a quotation mark does not break out of the attribute and corrupt the markup, or worse, inject markup into the page.
<input type="checkbox" name="newsletter" value="yes">
Check: For a checkbox, what does the value attribute determine?
Section 15 — Placeholder versus label
The placeholder attribute shows hint text inside a field, in a lighter colour, which disappears when the visitor starts typing, or when the field receives focus, depending on the browser. It is useful for showing an example of the expected format — "e.g. ada@example.com" in an email field, or "e.g. 0800 000 0000" in a phone field — or for giving a brief hint about what to enter, in a field whose label already says what it is for. That usefulness is real, but it is limited, and the placeholder's limitations are the reason it must never replace a label. A placeholder disappears, which means a visitor who starts typing and then forgets what the field wanted cannot refer back to the hint without clearing the field. A placeholder is not announced as a label by all screen readers, and when it is announced, it is often announced inconsistently, so a screen reader user may receive no name for the field at all, or may receive the placeholder as if it were a label, which it is not. And a placeholder's light colour often fails contrast requirements, making it hard to read for visitors with low vision, even before it disappears. Those three limitations — disappearing, unreliable announcement, and low contrast — make placeholder a poor substitute for a label, however clean a label-free design may look.
The correct relationship between placeholder and label is complementary: the label names the field, permanently and accessibly, and the placeholder, if used at all, gives an example or a hint, temporarily, in addition to the label. A field labelled "Email address", with a placeholder of "ada@example.com", is well designed: the label tells the visitor what the field is for, at all times, and the placeholder shows what an email address looks like, until the visitor starts typing their own. A field with only a placeholder of "Email address", and no label, is poorly designed: the hint disappears, the field has no accessible name, and the visitor who returns to the field later, after filling others, must guess what it was for. That distinction is not a matter of taste; it is a matter of whether the form remains usable after the visitor starts interacting with it, and a form that becomes less usable as it is used is a form that has failed at its most basic job. Labels persist. Placeholders do not. A field needs a persistent name, and only a label provides one.
There is a design temptation to omit labels for aesthetic reasons — a clean, minimal form, with only placeholder text inside its fields, looks modern and uncluttered, and it is a popular pattern in contemporary design. That pattern should be resisted, or, if it is adopted for visual reasons, it should be implemented with labels that are visually hidden but programmatically present, so that screen reader users still receive them, and with placeholder text that is treated as a hint, not as a name.
<label for="email">Email address</label>
<input id="email" name="email" type="email" placeholder="ada@example.com">
Check: Why must a placeholder never replace a label?
Section 16 — Required fields
The required attribute marks a field as mandatory: the browser will refuse to submit the form until that field has a value, and will show a message, and focus the field, if the visitor tries to submit without it. That built-in validation is one of the simplest and most effective form features, because it requires no JavaScript, it works consistently across browsers, and it prevents the most common form error — a visitor submitting a form with a crucial field left blank, either by oversight or because they did not realise the field was mandatory. Required should be applied to fields that are genuinely necessary for the form's purpose: a name for a contact form, an email for an account form, a message for a feedback form. It should not be applied to fields that are merely desirable, or that the author wishes the visitor would fill in, because making an optional field required forces visitors to invent answers, or to abandon the form, when they have no answer to give, which is a worse outcome than receiving a form with that field blank. The decision of what is required is a decision about the form's purpose, not about the author's preferences, and it should be made by asking, for each field, "can this form be acted on without this information?" If the answer is no, the field is required. If the answer is yes, it is not.
Required fields must be indicated visually, as well as programmatically, because a visitor should know before submitting which fields are mandatory, not discover it only when the browser refuses their submission. The conventional indicator is an asterisk, often with a note at the top of the form saying "Fields marked with * are required", and that convention is widely understood, though it should be accompanied by the required attribute itself, which is what makes the field programmatically required, for screen readers and for validation. A screen reader announces a required field as required, so that a blind visitor knows, when they reach the field, that it must be filled in, without having to submit and be told. A field that is visually marked as required but lacks the attribute is a field that looks mandatory but is not enforced, which confuses visitors who are told it is required and then find it is not, and a field that has the attribute but no visual indication is a field that surprises visitors on submission, which is a poor experience for everyone. Both indications — visual and programmatic — should be present, and they should agree.
There is a nuance about required and empty values that authors should understand. For a text field, required means the field must contain something other than whitespace — a field containing only spaces does not satisfy required, in most browsers, which is correct, because spaces are not an answer. For a checkbox, required means it must be checked, which is how agreement checkboxes are enforced.
<label for="name">Name *</label>
<input id="name" name="name" type="text" required>
Check: How should a visitor know a field is required before submitting?
Section 17 — Pattern validation
The pattern attribute lets an author specify, as a regular expression, the format a field's value must match, and the browser will refuse submission, with a message, if the value does not match. A regular expression is a compact notation for describing a pattern — for example, a code that must be three letters followed by three digits, or a username that must contain only letters, numbers, and underscores, between certain lengths. Pattern is powerful, because it allows validation of formats that no built-in type covers, without any JavaScript, and it is precise, because the author defines exactly what is acceptable. But that power comes with responsibility, because a pattern that is too strict will reject valid answers, and a pattern that is too loose will accept invalid ones, and both errors frustrate visitors or corrupt data. A pattern for a name that allows only letters, for example, will reject names with hyphens, apostrophes, spaces, or characters from other writing systems, which are all valid names, and which belong to real people who will be unable to submit the form. A pattern is a gate, and a gate that is built without understanding who needs to pass through it will exclude people it should admit.
Writing a good pattern requires testing, with both valid and invalid examples, and it requires a clear error message, because the browser's default message for a pattern failure — typically "Please match the requested format" — tells the visitor nothing about what the format actually is. The title attribute on a patterned field is conventionally used to describe the expected format, and browsers may show that title text as part of the validation message, which is one of the few cases where title serves a real purpose, though it should not be relied on alone, because title's limitations, covered earlier, still apply. The better practice is to describe the expected format in visible text near the field — in the label, in hint text, or in a note — so that the visitor knows the format before they type, not only after they fail. A field that says "Code: three letters followed by three digits, e.g. ABC123", with a pattern enforcing that format, is a field the visitor can succeed at on the first attempt. A field with only a pattern, and no visible description, is a field the visitor must fail at least once to learn what is expected, which is a poor experience, however correct the pattern may be.
Pattern should be used sparingly, and only where a format genuinely exists and can be described precisely. Codes, identifiers, postcodes in a known format, and usernames with defined rules are good candidates. Names, addresses, and free-form text are poor candidates, because their formats are too varied and too culturally specific to capture in a single expression, and attempting to do so will exclude valid answers. Pattern also does not replace server-side validation: a pattern can be bypassed by disabling JavaScript, by editing the page, or by submitting data directly, so the receiving system must validate again, regardless of what the browser checked.
<label for="code">Code (three letters, three digits)</label>
<input id="code" name="code" type="text" pattern="[A-Za-z]{3}[0-9]{3}">
Check: Why must a patterned field also describe its format in visible text?
Section 18 — Numbers, ranges, and dates
Numeric and date inputs bring the same benefits as other specific types — validation, appropriate keyboards, and meaning — but they have their own attributes and behaviours that deserve attention. A number input, type="number", accepts only numeric values, and it can be constrained with min, max, and step attributes: min sets the smallest acceptable value, max the largest, and step sets the interval between acceptable values, so that a field with min="0", max="10", and step="2" accepts 0, 2, 4, 6, 8, and 10, but not 1 or 3. Browsers typically render number inputs with small increment and decrement controls, which let the visitor adjust the value without typing, and they validate the value against min, max, and step on submission, refusing values outside the range or off the step. That validation is useful for quantities, ages, and other genuinely numeric data, but it must be used with the data's real constraints in mind: a step of 1 is correct for a count of people, but wrong for a measurement that can be fractional, and a max that is too low will reject valid answers, just as a pattern that is too strict does.
A range input, type="range", is a slider, for choosing a value within a range, rather than typing an exact number. It uses the same min, max, and step attributes, and it also has a value, which sets the slider's initial position. Range inputs are appropriate when the exact value matters less than its approximate position — a rating, a volume, a preference on a scale — and when the visitor benefits from seeing the range visually, rather than typing a number. They are not appropriate when precision matters, because a slider is a poor tool for entering an exact value, particularly on a touch screen, where fine adjustment is difficult. A range input should therefore be paired with a visible display of its current value, often an output element, covered later in this chapter, so that the visitor knows what value they have selected, rather than guessing from the slider's position. Without that display, a range input asks the visitor to choose blindly, which is a poor experience, however attractive the slider may look.
Date, time, and related inputs — date, time, month, week, and datetime-local — accept calendar and clock values, and browsers render them with native pickers: a calendar for dates, a clock interface for times, which are familiar to the device's user and which handle the complexities of date formats, leap years, and time zones without the author having to code them. Those inputs also accept min and max, as dates or times, to constrain the acceptable range — a booking form that only accepts future dates, for example, can set min to today, though setting min to a dynamic value like today requires JavaScript, because HTML attributes are static. The values submitted by date and time inputs are in a standard format — YYYY-MM-DD for dates, HH:MM for times — regardless of how the browser displays them, which simplifies the receiving system's job, because it does not have to parse the many formats in which humans write dates.
<label for="age">Age</label>
<input id="age" name="age" type="number" min="0" max="120">
Check: What do min, max, and step control on a number input?
Section 19 — Disabled and readonly fields
Disabled and readonly are two ways to make a field uneditable, and they differ in important ways that affect submission, focus, and meaning. A disabled field, marked with the disabled attribute, is completely inactive: the visitor cannot focus it, cannot edit it, and cannot interact with it in any way, and, crucially, its value is not submitted with the form. A disabled field is effectively absent from the form's data, even though it is visible on the page, which makes disabled appropriate for fields that are not applicable in the current context — a field that only applies when a certain option is chosen, for example, and which should not contribute data when that option is not chosen. A readonly field, marked with readonly, is different: the visitor cannot edit it, but they can focus it, select its text, and copy it, and its value is submitted with the form, like any editable field's. Readonly is appropriate for a value the visitor should see and that should be submitted, but should not change — a pre-filled account number on a confirmation form, for example, or a calculated total that the visitor should review but not edit.
The choice between disabled and readonly is therefore a choice about whether the field's value should be part of the submission, and whether the visitor should be able to interact with it at all. A field that is disabled but whose value is needed by the receiving system is a bug, because the system will receive no value, and will have to guess, or fail, without knowing why. A field that is readonly but whose value the visitor should be able to change is also a bug, because the visitor is prevented from correcting an error, and may submit a wrong value they could see but could not fix. The two attributes look similar — both make a field uneditable — but their submission behaviour is opposite, and that opposition is the whole point of having two attributes instead of one. Testing a form must include checking disabled and readonly fields specifically: submit the form, and verify that disabled fields' values are absent, and readonly fields' values are present, because that verification is the only way to confirm the attributes are doing what the author intended.
There is also an accessibility dimension. A disabled field is typically rendered greyed out, and screen readers announce it as disabled, so that a visitor knows it is inactive and will not try to use it. A readonly field is rendered normally, or nearly so, and screen readers announce it as readonly, so that a visitor knows they can read it but not change it. Those announcements matter, because a visitor who encounters a field they cannot edit, without knowing why, may think the form is broken, or may waste time trying to interact with it. The author should therefore make the reason for a field's disabled or readonly state clear, in visible text where possible — a note explaining why a field is disabled, or why a value cannot be changed — rather than leaving the visitor to guess.
<label for="account">Account number</label>
<input id="account" name="account" type="text" value="12345" readonly>
Check: Which one submits its value — a disabled field or a readonly field?
Section 20 — Autocomplete
The autocomplete attribute tells the browser what kind of information a field expects, so that the browser can offer to fill it from the visitor's saved information — their name, address, email, phone number, payment details, and other personal data they have chosen to save. That offer is a significant convenience, particularly on a phone, where typing a full address is slow and error-prone, and it is also a significant accessibility benefit, because it reduces the amount of typing a visitor must do, which helps visitors with motor difficulties, with cognitive difficulties, and with any other condition that makes typing hard. The autocomplete attribute's values are a standardised vocabulary — "name", "email", "tel", "street-address", "postal-code", "country", and many others — and using the correct value for each field is what enables the browser to match saved information to the right field. A field marked autocomplete="email" will be offered the visitor's saved email address. A field marked autocomplete="off", or with no autocomplete attribute, will not receive that offer, or will receive it only if the browser guesses correctly, which it may not.
Autocomplete should be set on every field that collects personal information, because the cost of setting it is a single attribute, and the benefit is a faster, easier form for every returning visitor. It should also be set correctly, because an incorrect value is worse than no value: a field for a shipping address marked as a billing address will be filled with the wrong address, and a field for a username marked as a name will be filled with the visitor's full name, both of which are errors the visitor must then correct, which is worse than typing from scratch. The standard vocabulary includes distinctions that matter: "shipping" versus "billing" address fields, "given-name" versus "family-name", "home" versus "work" telephone numbers, and "current-password" versus "new-password" for login and registration forms respectively. Those distinctions exist because browsers need them to fill forms correctly, and authors who learn the relevant subset for their forms — which is usually a handful of values, not the whole vocabulary — will produce forms that browsers can fill reliably, which is a service to the visitor that costs the author almost nothing.
There is a common misconception that autocomplete="off" improves privacy or security, and that it should be applied broadly to prevent browsers from saving or filling information. In practice, browsers often ignore autocomplete="off" for login and password fields, because they have decided that helping visitors use strong, unique passwords, via a password manager, is more secure than forcing visitors to memorise passwords or reuse weak ones. For other fields, autocomplete="off" does prevent the browser from offering saved information, but it also prevents the convenience that saved information provides, and it should be used only where there is a genuine reason — a field for a one-time code, for example, where saved information would be wrong, or a field in a shared-device context where autofill could expose one person's data to another. For ordinary personal-information fields, autocomplete should be on, and correct, because the visitor has chosen to save that information, and the browser is acting on their choice, not overriding it.
<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email">
Check: What does a correct autocomplete value enable the browser to do?
Section 21 — Autofocus, used with care
The autofocus attribute tells the browser to focus a field automatically when the page loads, placing the cursor in it, so that the visitor can start typing immediately, without clicking or tabbing to the field first. That sounds convenient, and for a page whose sole purpose is a single field — a search page, for example, where the visitor has arrived specifically to search — it is convenient, because it saves the visitor one action, and it matches their intent, which is to type into that field. For most other pages, however, autofocus is a hindrance, not a help, and it should be used with great care, or not at all. The reason is that focusing a field is not a neutral act: it moves the visitor's attention, and on some devices, it triggers other behaviours, such as opening a mobile keyboard, which covers a large part of the screen, or scrolling the page to bring the field into view, which displaces the content the visitor was reading. A visitor who arrives on a page to read its introduction, and finds their screen scrolled to a form field, with a keyboard open, has had their intent overridden by the page, and that is a poor experience, however well-intentioned the autofocus may have been.
Autofocus is particularly problematic for screen reader users and keyboard users. A screen reader announces the focused element when a page loads, and if that element is a form field in the middle of a page, the visitor hears the field, not the page's title, heading, or introduction, and they lose the context that would tell them where they are and what the page is for. A keyboard user who tabs through a page expects to start at the beginning, and to move through its content in order; autofocus jumps them to a field, skipping the content before it, and forcing them to navigate backwards to find what they missed. Those are not minor inconveniences; they are disruptions to the basic ways those visitors navigate, and they are caused by a single attribute that the author added to save a click. The guideline, therefore, is to use autofocus only on pages where the field is the page's entire purpose, and where the visitor's intent on arrival is unambiguously to use that field — a dedicated search page, a login page where the visitor has just clicked "sign in", or a similar single-purpose page. On any page with other content, or with multiple fields, autofocus should be omitted, and the visitor should be left to choose their own starting point.
There is also a technical limit: only one element on a page should have autofocus, because only one element can be focused at a time, and if several elements claim autofocus, the browser will focus the last one, or behave unpredictably, depending on the browser. That limit reinforces the guideline: autofocus is for a single, primary field, not for every field in a form, and a form whose fields all claim autofocus is a form whose author has misunderstood the attribute.
<label for="search">Search</label>
<input id="search" name="q" type="search" autofocus>
Check: Why can autofocus harm a screen reader user's experience?
Section 22 — Fieldset and legend
A fieldset element groups related fields together, and its legend element names the group, providing a heading for the set of fields it contains. That grouping is structural, not merely visual: browsers render a fieldset with a border by default, which visually groups its fields, but the more important grouping is programmatic, for screen readers, which announce the legend when a visitor enters the fieldset, and repeat it, or make it available, as the visitor moves through its fields, so that they always know which question the fields they are answering belong to. A group of radio buttons for delivery method, wrapped in a fieldset with a legend of "Delivery method", is announced to a screen reader user as a group, with its question, before the individual options are read, which gives the options context they would otherwise lack. Without the fieldset, the same radio buttons would be announced as a series of unrelated options, and the visitor would have to infer from the surrounding text what question they answer, which is a burden that a fieldset removes with a single element.
Fieldset is appropriate whenever a form contains a set of fields that answer a single question, or that belong to a single topic. Radio groups and checkbox groups are the classic cases, because their options are meaningless without their question, but fieldsets also suit larger groupings: personal details, delivery address, billing address, payment details — each a section of a longer form, with its own legend, so that the form is divided into understandable parts, rather than presented as one long, undifferentiated list of fields. That division helps all visitors, not just screen reader users, because a long form divided into sections with headings is easier to scan, easier to complete in stages, and easier to return to, than a long form with no divisions. The legend serves as the section's heading, and it should be concise and descriptive, like any heading: "Delivery address" is good, "Section 2" is not, because it tells the visitor nothing about what the section contains. A fieldset without a legend is incomplete, in the same way a section without a heading is incomplete: the group exists, but it has no name, and a group without a name cannot be announced or navigated as a group.
There is a styling consideration that has historically discouraged fieldset use: fieldsets are notoriously difficult to style consistently across browsers, particularly their borders and legends, which browsers render with special rules that CSS does not fully control. That difficulty is real, but it is not a reason to abandon fieldsets, because their structural and accessibility benefits outweigh their styling quirks, and because modern CSS handles fieldsets better than older CSS did. An author who needs precise visual control can style the fieldset's border, or remove it, and style the legend, within the limits browsers allow, and can achieve a clean result with a little experimentation.
<fieldset>
<legend>Delivery address</legend>
<label>Street <input name="street"></label>
</fieldset>
Check: What does a screen reader announce when a visitor enters a fieldset?
Section 23 — Hidden inputs
A hidden input, type="hidden", is a field that is not displayed to the visitor, but whose name and value are submitted with the form, like any other field's. Its purpose is to carry information that the form needs to send, but that the visitor does not need to see or edit: an identifier for the item being edited, a token that identifies the form's session, a flag indicating which version of a page the form came from, or a value that the server needs to process the submission correctly, but that has no meaning to the visitor. A form editing an existing article, for example, might include a hidden input with the article's identifier, so that the server knows which article to update, without showing the identifier to the visitor, who does not need to know it and should not be able to change it accidentally. A form that is part of a multi-step process might include a hidden input with the current step, so that the server knows where the visitor is in the process, without requiring the visitor to remember or to state it.
Hidden inputs must be understood for what they are, and for what they are not. They are hidden from display, but they are not hidden from the visitor: anyone can view the page's source and see a hidden input's name and value, and anyone with basic tools can change that value before submitting, which means hidden inputs must never be trusted as a security mechanism. A hidden input carrying a price, for example, must not be trusted by the server, because a visitor can change the price to a lower value and submit it, and a server that accepts the submitted price without checking it against its own records will be defrauded. A hidden input carrying a permission flag, or a user identifier, must not be trusted for the same reason: the server must verify permissions and identity from its own session data, not from a value the visitor supplied, however hidden it appeared to be. Hidden inputs are a convenience for carrying non-sensitive, non-authoritative data — identifiers that the server will verify, tokens that the server will validate, flags that the server will check — not a vault for secrets or a lock for permissions. Anything that must be secure must be verified server-side, regardless of how it arrives.
There is also a question of when not to use a hidden input. If the information is needed by the server but is already known to the server — from the visitor's session, for example — then sending it in a hidden input is redundant, and it introduces a risk that the sent value and the server's value will disagree, creating a bug where none needed to exist. If the information is something the visitor should be able to see, even if they cannot edit it, then a readonly field is more appropriate than a hidden one, because it shows the visitor what is being submitted on their behalf, which is a matter of transparency.
<input type="hidden" name="article_id" value="42">
Check: Why must a server never trust a hidden input's value blindly?
Section 24 — File inputs
A file input, type="file", lets a visitor choose a file from their device to upload with the form — a photograph, a document, a resume, or any other file the form is designed to receive. When the visitor clicks the file input, their device opens a file picker, from which they choose a file, and the input then shows the file's name, so that the visitor can confirm they chose the right one before submitting. The file input's accept attribute can suggest which kinds of files are acceptable, by file type or extension — "image/*" for any image, ".pdf" for PDF documents, "image/png, image/jpeg" for specific image formats — and the file picker will filter its display accordingly, though accept is only a suggestion, not a security control, because a visitor can bypass it, and the server must validate the uploaded file's type and size itself. The multiple attribute allows the visitor to choose several files at once, which is appropriate for galleries, attachments, and other multi-file uploads, and which changes the submitted data to a set of files, rather than a single one, which the receiving system must handle accordingly.
File uploads have a form-level requirement that beginners often miss: a form containing a file input must use method="post" and enctype="multipart/form-data". The enctype attribute, covered in more detail later, controls how the form's data is encoded for submission, and the default encoding, which sends data as a query-like string, cannot represent a file's binary content. Multipart encoding, by contrast, sends each field, including files, as a separate part of the message, with its own headers, which allows binary data to travel intact. A form with a file input but without multipart encoding will submit the file's name, but not its content, which is a confusing failure, because the visitor believes they have uploaded a file, and the server receives only its name, with no indication that the content is missing. That failure is silent and embarrassing, and it is prevented by a single attribute on the form element, which should be added at the same moment as the file input, as a matter of habit, whenever a file input is introduced.
There are user-experience considerations for file inputs that go beyond their basic function. A file input's appearance is controlled largely by the browser and operating system, and it is difficult to style consistently, which has led to a common pattern: hiding the file input itself, and styling a label or a button to trigger it, so that the upload control matches the site's design. That pattern is acceptable, provided the label is correctly associated, so that clicking it opens the file picker, and provided the chosen file's name is displayed, so that the visitor can confirm their choice. What should not be done is to hide the file input without providing any trigger or feedback, leaving the visitor with no way to upload, or no confirmation of what they uploaded.
<form action="/upload" method="post" enctype="multipart/form-data">
<label for="photo">Photo</label>
<input id="photo" name="photo" type="file" accept="image/*">
<button type="submit">Upload</button>
</form>
Check: What enctype must a form with a file input use?
Section 25 — Colour, date, and time inputs
Beyond text, number, and file inputs, HTML offers specialised inputs for colours, dates, and times, each with a native picker that handles the complexity of its data type without the author having to build a custom control. A colour input, type="color", presents a colour picker, from which the visitor chooses a colour, and submits it as a hexadecimal value, like #ff0000 for red. It is appropriate for settings, themes, and customisation forms, where the visitor is choosing a colour, not typing a code, and where a visual picker is more intuitive than a text field for a hex value, which most visitors cannot write from memory. A date input, type="date", presents a calendar picker, and submits a date in the standard YYYY-MM-DD format, regardless of how the browser displays it, which may vary by locale. A time input, type="time", presents a time picker, and submits a time in HH:MM format. Month, week, and datetime-local inputs extend the same idea to months, weeks, and combined date-and-time values, though they are less widely used, and their support varies more across browsers, which is worth checking before relying on them for a critical form.
The value of these native pickers lies in what they prevent, as much as in what they provide. A date picker prevents impossible dates — February 30, for example — because the calendar simply does not offer them, whereas a text field invites any string, including impossible ones, and requires the author to validate and to explain the expected format. A colour picker prevents invalid colour codes, because the visitor chooses from real colours, rather than typing a code that may be mistyped. A time picker prevents ambiguous times, because it produces a standard format, rather than inviting "3pm", "15:00", "3:00 PM", and every other variation, which the receiving system must then parse. That prevention is a form of validation that happens before submission, without any error message, because the error was never allowed to occur, which is the best kind of validation: the kind the visitor never notices, because they never make the mistake it would have caught. Native pickers also benefit from familiarity: a calendar picker on a phone looks like the phone's own calendar, and a colour picker looks like the system's own picker, so the visitor already knows how to use them, without learning a custom control designed for one site alone.
Native inputs have limits, and those limits should be understood before choosing them over a text field or a custom control. Their appearance varies across browsers and operating systems, because each platform renders its own picker, which means a form using native date inputs will look different on different devices, and an author who needs pixel-perfect consistency across platforms will be disappointed.
<label for="birthday">Birthday</label>
<input id="birthday" name="birthday" type="date">
<label for="theme">Theme colour</label>
<input id="theme" name="theme" type="color">
Check: What format does a date input submit, regardless of how it displays?
Section 26 — Range sliders and output
A range input, type="range", is a slider for choosing a value within a range, and an output element is the place to display the result of a calculation, or the current value of a control like a slider, so that the visitor can see it. The two are often used together, because a slider on its own does not show its value precisely: the visitor can see the slider's position, but not whether it represents 42 or 47, or 60% or 65%, and that imprecision matters when the value will be submitted, or when it affects something else on the page. An output element, associated with the slider through its for attribute, or updated by JavaScript as the slider moves, displays the value in text, so that the visitor knows exactly what they have chosen. That pairing — a control for choosing, and an output for showing — is a pattern that applies beyond sliders: any input whose value is not self-evident should be accompanied by a display of that value, so that the visitor is never choosing blindly.
The range input's attributes — min, max, step, and value — define its behaviour, as with number inputs, but its interaction is different: dragging a handle, rather than typing a number, which is intuitive for approximate choices and poor for precise ones. A rating from 1 to 5, a volume from 0 to 100, a preference on a scale from "not at all" to "very much" — these are range-appropriate, because the visitor's intent is approximate, and the exact number matters less than its position on the scale. A price, a quantity, or a measurement, where the exact value matters, is not range-appropriate, because a slider cannot reliably select an exact value, particularly on a touch screen, where fine dragging is difficult. The output element helps with precision, by showing the value, but it does not fix the interaction: a visitor who needs to enter exactly 37, and who is given a slider from 0 to 100, will struggle to land on 37, however clearly the output displays their attempts. For precise values, a number input is the correct control, and a range input should be reserved for the approximate choices it serves well.
Output elements have a broader use than displaying slider values: they display the result of any calculation performed on the page, such as a total, a converted value, or a summary, in a way that is semantically marked as output, rather than as ordinary text. A screen reader can announce an output element's content when it changes, which is useful for a total that updates as the visitor changes quantities, or for a conversion that updates as they type, because the visitor hears the result without having to navigate to it. That live announcement should be used carefully — an output that updates on every keystroke can become noisy — but for calculations that update on a deliberate action, like moving a slider or pressing a calculate button, it is a valuable accessibility feature.
<label for="volume">Volume</label>
<input id="volume" name="volume" type="range" min="0" max="100" value="50">
<output for="volume">50</output>
Check: Why should a range slider be paired with a visible value display?
Section 27 — Datalists for suggestions
A datalist element provides a list of suggested values for a text field, combining the flexibility of free typing with the convenience of predefined choices. The datalist is given an id, and the text field is given a list attribute whose value matches that id, linking the two. When the visitor focuses the field and starts typing, the browser shows the datalist's options as suggestions, filtered by what the visitor has typed, and the visitor can choose one, or can ignore the suggestions and type their own value, which is then submitted as entered. That combination — suggestions without restriction — is datalist's defining feature, and it distinguishes it from a select menu, which restricts the visitor to its options, and from a plain text field, which offers no suggestions at all. Datalist is appropriate when the common answers are known, but other answers are possible: a list of common cities, where a visitor might live in a city not listed; a list of common job titles, where a visitor's title might be unusual; or a list of popular search terms, where the visitor's search might be novel. In each case, the suggestions save typing and prevent spelling errors for the common cases, without excluding the uncommon ones.
The options inside a datalist are option elements, like those in a select menu, but their role is different: they are suggestions, not choices, and their values are what the visitor's input is compared against, while their text, if present, can provide a label for the suggestion. A datalist option with only a value shows that value as the suggestion. An option with a value and text, or with a label attribute, can show a more descriptive suggestion, though browsers vary in how they display that description, and authors should test the display on their audience's browsers before relying on it. The datalist itself is not rendered as a visible element on the page; it exists only as a source of suggestions, which appear when the linked field is used. That invisibility means a datalist does not take up space on the page, which is an advantage over a select menu for long lists, but it also means a visitor who does not start typing will never see the suggestions, which is a limitation: datalist suggests, it does not present, and a visitor who needs to browse the options, rather than recall them, will not find them in a datalist.
Datalist's browser support is now broad, but it has historically varied, particularly in how suggestions are displayed and filtered, and authors should test it on their audience's browsers, and should not rely on it as the only way to provide choices. For a field where the choices must be restricted — a country list for a shipping form, where only listed countries are served — a select menu is correct, because datalist would allow an unlisted country to be submitted, which the system cannot serve. For a field where suggestions are a convenience, not a restriction, datalist is ideal, because it helps without constraining.
<label for="city">City</label>
<input id="city" name="city" type="text" list="cities">
<datalist id="cities">
<option value="Lagos">
<option value="Abuja">
<option value="Ibadan">
</datalist>
Check: What can a visitor do in a datalist field that they cannot do in a select menu?
Section 28 — Built-in browser validation
Browsers validate forms automatically, using the attributes authors provide: required, type, pattern, min, max, step, and maxlength, among others. When a visitor submits a form that fails validation, the browser stops the submission, focuses the first failing field, and shows a message explaining the problem, in the browser's own style and language. That built-in validation is valuable for three reasons: it requires no JavaScript, so it works even when scripts fail; it is consistent within each browser, so visitors encounter familiar messages and behaviours across sites; and it is accessible, because browsers expose validation messages to screen readers, so that a blind visitor hears what is wrong and which field needs fixing, without the author having to code that announcement. Built-in validation is therefore the first line of defence for form correctness, and it should be used wherever its attributes can express the rule, before reaching for JavaScript validation, which is more flexible but also more work, and more prone to accessibility errors if implemented carelessly.
Built-in validation has limits that authors must understand. Its messages are controlled by the browser, not by the author, and while an author can influence them — through the title attribute for pattern fields, or through JavaScript's custom validity API — they cannot fully control their wording, styling, or language, which means a site that needs precise, branded, or multilingual error messages will need JavaScript validation as well, or instead. Its rules are also limited to what attributes can express: a field that must match another field — a password confirmation, for example — cannot be validated by attributes alone, because no attribute says "this field must equal that field", and that rule requires JavaScript. And its behaviour, while consistent within a browser, varies across browsers, in message wording, in styling, and in exactly when validation is triggered, which means an author who relies solely on built-in validation must test it on multiple browsers, to ensure the experience is acceptable on all of them, even if it is not identical. Those limits do not diminish built-in validation's value; they define its role, as the foundation on which custom validation is built, not as a replacement for it.
The best practice is to use built-in validation as the base, and to add JavaScript validation only where built-in validation cannot express the rule, or where the messages need to be customised for clarity. That layered approach — built-in first, custom second — ensures that a form is validated even when JavaScript fails, because the built-in layer remains, and that it is validated precisely when JavaScript works, because the custom layer adds the rules the built-in layer cannot express. It also ensures that validation messages are accessible by default, because the browser's messages are, and that custom messages, when added, must meet the same accessibility standard, by being associated with their fields and announced to screen readers, which is a requirement the author must meet deliberately when writing custom validation. A form that relies only on JavaScript validation, with no built-in attributes, is a form that fails open when scripts fail, accepting invalid data without complaint, which is a worse failure than any validation message could be.
<label for="email">Email address</label>
<input id="email" name="email" type="email" required>
Check: What does the browser do when a required field is empty on submission?
Section 29 — Turning validation off
The novalidate attribute, on a form, tells the browser not to perform its built-in validation when the form is submitted, and the formnovalidate attribute, on a submit button, does the same for submissions triggered by that button alone. Those attributes exist for specific cases where built-in validation would interfere with the form's purpose, and they should be used deliberately, not as a way to avoid validation altogether. The classic case is a "save draft" button: a form for writing an article, for example, where the visitor may want to save an incomplete draft, without filling in every required field, and where the browser's refusal to submit an incomplete form would prevent saving. A save-draft button with formnovalidate allows that partial submission, while the main publish button, without formnovalidate, still enforces full validation, so that a draft can be saved incomplete, but a published article must be complete. That distinction — between saving and publishing, between a draft and a final version — is the legitimate use of turning validation off, and it is a use where the attribute serves the visitor's intent, rather than undermining it.
Another legitimate case is a form that is validated entirely by JavaScript, with custom messages and custom rules that built-in validation cannot express, and where built-in messages would conflict with or duplicate the custom ones. In that case, novalidate on the form prevents the browser's messages from appearing alongside the custom ones, which would confuse the visitor with two sets of errors for the same problem. That case requires care, because turning off built-in validation removes the safety net that built-in validation provides when JavaScript fails, and a form with novalidate and JavaScript validation that fails to load, or fails to run, will submit invalid data without any check at all. The remedy is to ensure that JavaScript validation is robust, and that the server validates again, as it always must, because client-side validation of any kind — built-in or custom — can be bypassed, and server-side validation is the only validation that cannot be. Novalidate is therefore not a way to avoid validation; it is a way to replace one kind of client-side validation with another, and the replacement must be at least as good as what it replaces, in correctness and in accessibility, or the form is worse for it.
What novalidate must never be used for is to make a form easier to submit by removing its checks, without providing an alternative. A form whose required fields are bypassed by novalidate, with no other validation, is a form that accepts incomplete data, which the receiving system must then handle — by rejecting it, with a confusing server error, or by storing it incomplete, which corrupts the data. That is not a simplification; it is a transfer of the problem from the visitor's browser, where it could have been fixed immediately, to the server, where it is fixed later, with more effort, and with a worse experience for the visitor, who must return to the form and correct it after the fact.
<form novalidate>
<!-- fields validated by JavaScript instead -->
</form>
Check: When is formnovalidate on a save-draft button appropriate?
Section 30 — Encoding form data
The enctype attribute on a form controls how its data is encoded for submission, and it matters most for file uploads, though it affects all forms. The default encoding, application/x-www-form-urlencoded, encodes the form's data as a string of name-value pairs, joined by ampersands, with special characters escaped, similar to a query string. That encoding is compact and universally supported, and it is correct for forms without file inputs, which is the majority of forms, because their data is text, and text encodes cleanly as name-value pairs. It is not correct for forms with file inputs, because a file's binary content cannot be represented as a text pair without corruption, and a form that tries to do so will submit the file's name but not its content, as described in the file input section. For file forms, the correct encoding is multipart/form-data, which sends each field as a separate part, with its own headers, allowing binary data to travel intact alongside text fields, which is why multipart is the required enctype for any form containing a file input, without exception.
A third encoding, text/plain, exists, but it is rarely used, because it encodes data in a human-readable but poorly standardised format, which most receiving systems do not parse reliably, and it offers no advantage over the default for ordinary forms. It appears occasionally in mailto forms, where a form's action is a mailto address, and the data is intended to become the body of an email, but mailto forms are themselves unreliable — they depend on the visitor's email program, they expose data, and they fail silently when no email program is configured — and they should generally be replaced by a server-side form handler, which receives the data properly encoded and processes it reliably. The existence of text/plain is therefore more of a historical footnote than a practical option, and authors should not choose it without a specific, well-understood reason, and a receiving system that is known to handle it. For practical purposes, a form's enctype is a binary choice: the default, for text forms, or multipart, for file forms, and that choice should be made at the same moment as the decision to include a file input, because the two decisions are linked.
The enctype attribute also interacts with the method attribute, in a way worth noting. GET forms always use the default, query-like encoding, because their data travels in the address, regardless of enctype, which is ignored for GET. POST forms respect enctype, and can use either the default or multipart, depending on whether they contain files. That interaction means an author who sets enctype="multipart/form-data" on a GET form has made an error that will be silently ignored, and an author who sets it on a POST form without files has made a choice that is harmless but unnecessary, because multipart encoding is less compact than the default for text-only data, and it requires the receiving system to parse multipart data, which it may not expect. The cleanest practice is to leave enctype unset for text-only forms and set it to multipart for forms with file inputs, where it is required.
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="photo">
</form>
Check: Why can't the default encoding carry a file's content?
Section 31 — Hint text and descriptions
Hint text is the additional information that helps a visitor fill in a field correctly, beyond what its label says: an example format, a constraint, a note about what will happen to the information, or a clarification of an ambiguous term. A label says "Password". Hint text says "At least 8 characters, including a number". A label says "Username". Hint text says "This will be visible to other users". That additional information is essential for fields where the label alone is insufficient, and it should be provided in visible text near the field, not hidden in a tooltip, a placeholder, or a title attribute, for the same reasons those channels are weak: they disappear, or they are unavailable to some visitors, or they are hard to find. Visible hint text, placed beneath or beside the field, is available to every visitor, at the moment they need it, which is when they are deciding what to enter, not after they have failed validation and are trying to understand why.
Hint text also has a programmatic association, through the aria-describedby attribute, which links a field to the element containing its hint, by id, so that screen readers announce the hint when the visitor reaches the field, in addition to its label. That association is important, because a screen reader user navigating by fields will hear the label, but will not necessarily hear nearby hint text, unless it is explicitly associated, and they may therefore miss information that sighted visitors see at a glance. The aria-describedby attribute is not a substitute for visible hint text — the hint must still be visible, for sighted visitors — but it is the mechanism that makes visible hint text available to screen reader users as well, in the right order, at the right moment. A field with a hint that is visible but not associated is a field whose hint serves only some visitors, and a field with a hint that is associated but not visible is a field whose hint serves only screen reader users, which is the opposite failure. Both forms — visible and associated — should be present, for any hint that matters to completing the field correctly.
There is a distinction between hint text and error messages, covered next, that should be kept clear. Hint text helps the visitor succeed before they act: it tells them what is expected, so that they can provide it correctly the first time. An error message helps the visitor recover after they have failed: it tells them what went wrong, and how to fix it. Both are needed, and they serve different moments in the interaction, which is why a field can have both a hint and an error, associated through aria-describedby with multiple ids, so that a screen reader announces both when relevant. A field whose only guidance is its error message is a field that teaches by failure, which is a poor way to teach, because it wastes the visitor's time and patience.
<label for="password">Password</label>
<input id="password" name="password" type="password" aria-describedby="password-hint">
<p id="password-hint">At least 8 characters, including a number.</p>
Check: How does aria-describedby help a screen reader user with hint text?
Section 32 — Error messages
An error message tells a visitor what went wrong with a field, and how to fix it, after validation has failed. Good error messages are specific, polite, and actionable: they name the field, describe the problem in plain language, and suggest the correction, rather than merely stating that something is invalid. "Email address must contain an @ sign" is a good error message, because it names the field, describes the problem, and implies the fix. "Invalid input" is a poor error message, because it names nothing, describes nothing, and suggests nothing, leaving the visitor to guess which field failed and why. "Error!" is worse, because it adds alarm without information. The quality of a form's error messages is often the quality of the form itself, because errors are the moments when a visitor is most frustrated, most likely to abandon the form, and most in need of clear guidance. A form that errors well retains visitors; a form that errors poorly loses them, however good its successful path may be.
Error messages must be associated with their fields, just as hints are, so that screen reader users hear them when they reach the field, and so that the association is clear to all visitors. For built-in browser validation, that association is automatic: the browser shows its message for the failing field, focuses that field, and announces the message to screen readers. For custom JavaScript validation, the author must create that association deliberately, typically by placing the error message in an element near the field, giving it an id, and linking it with aria-describedby, and by giving it a role of alert, or by ensuring it is announced when it appears, so that a screen reader user hears the error without having to navigate to it. An error message that appears visually but is not announced is an error message that serves only sighted visitors, and a form whose errors are invisible to some visitors is a form that traps those visitors in a loop: they submit, the submission fails, they hear nothing about why, they submit again, and it fails again, with no explanation they can access. That loop is one of the most frustrating experiences on the web, and it is entirely preventable by associating errors correctly.
Error messages should also be presented in a way that helps the visitor recover efficiently. When a form fails validation, the visitor should be able to see, at a glance, which fields failed, without having to hunt through the form, which means failed fields should be visually distinguished — with a border, an icon, or a colour, in addition to text, because colour alone is insufficient for visitors who cannot distinguish it — and their error messages should be placed where the visitor will find them, typically immediately beside or beneath the field, not in a summary at the top of the form alone, though a summary can supplement individual messages, by listing all errors and linking to their fields, which is helpful for long forms.
<label for="email">Email address</label>
<input id="email" name="email" type="email" aria-describedby="email-error">
<p id="email-error">Please enter a valid email address, e.g. ada@example.com.</p>
Check: What makes an error message actionable rather than merely stating "invalid"?
Section 33 — Naming fields consistently
Field names should follow a consistent convention across a form and across a site, because names are the interface between the page and the receiving system, and an inconsistent interface is harder to use, harder to maintain, and harder to debug. The convention itself matters less than its consistency: whether names use underscores (first_name), camelCase (firstName), or hyphens (first-name), the important thing is that the same kind of data uses the same style everywhere, so that a developer writing receiving code can predict a field's name from its meaning, without having to look it up, and so that a developer maintaining a form can tell, from its names, which fields belong together and which are outliers. Inconsistency, by contrast, forces every developer to check every name, because no name can be trusted to follow the pattern, and it creates bugs where a receiving system expects "email" but receives "e-mail", or expects "phone" but receives "telephone", and the data is lost or misfiled, not because it was wrong, but because its name did not match the system's expectation.
Consistency also applies to the relationship between a field's name, its id, and its label. Those three should agree, in meaning if not in exact text: a field labelled "Email address", with id "email" and name "email", is coherent, because all three refer to the same thing, in the same terms, and a developer reading any one of them can infer the others. A field labelled "Email address", with id "contact-field-3" and name "x", is incoherent, because its three identifiers tell three different stories, and a developer must hold all three in mind to understand the field, which is a cognitive burden that grows with every field in the form. Coherence does not require the id and name to be identical — they serve different purposes, as covered earlier, and they may differ in style for technical reasons — but it does require them to be recognisably about the same data, so that the field's identity is clear from any angle. A form whose fields are coherently named is a form whose code reads like a description of its purpose, and a form whose fields are incoherently named is a form whose code reads like a puzzle, which is a poor way to describe a purpose.
Naming consistency should be established early, documented briefly, and enforced by review, because names, once used in a live form, become difficult to change, as they are embedded in receiving code, in databases, and in integrations. A naming convention document need not be long: a page that says "use lowercase with underscores, use full words not abbreviations, use the same name for the same data everywhere" is sufficient, provided it is followed. New fields should be checked against it, and old fields that violate it should be renamed only as part of a coordinated change, with the receiving system updated at the same time, because renaming a field in the page without renaming it in the receiver breaks the form silently, in the same way as omitting a name breaks it.
<label for="first_name">First name</label>
<input id="first_name" name="first_name" type="text">
<label for="last_name">Last name</label>
<input id="last_name" name="last_name" type="text">
Check: Why should the same kind of data use the same name style everywhere?
Section 34 — The reset button, used rarely
A reset button, type="reset", restores a form's fields to their initial values, clearing anything the visitor has entered, or, for fields with pre-filled values, restoring those values. It is a destructive action, because it discards the visitor's work, and it should be used rarely, and with caution, for that reason. The classic case for a reset button is a form where the visitor may want to start over — a complex search form with many filters, for example, where clearing all filters at once is genuinely useful, and where re-entering the filters would be tedious. In that case, a reset button labelled clearly, such as "Clear filters", placed where it will not be mistaken for the submit button, can be a convenience. In most other cases, particularly in forms where the visitor enters personal information, a message, or an order, a reset button is a hazard, because it sits near the submit button, looks similar to it, and a visitor who clicks it by mistake loses everything they have typed, with no undo, and with no warning, unless the author has added one. That loss is frustrating, and it is entirely avoidable by omitting the reset button, which is what most forms should do.
The danger of reset buttons is compounded by their appearance and position. A reset button is often styled like a secondary button, beside the primary submit button, and on a small screen, or for a visitor with motor difficulties, the two buttons are easy to confuse, particularly if their labels are similar — "Submit" and "Reset", for example, are both short, both verbs, and both plausible actions at the end of a form, which makes a misclick likely. A visitor who intended to submit and instead resets will discover their error only when the form clears, at which point their work is gone, and they must decide whether to re-enter it or to abandon the form, neither of which is a good outcome. The remedy, where a reset function is genuinely needed, is to make it unmistakable: label it clearly as a clearing action, such as "Clear all fields", place it away from the submit button, or require confirmation before it acts, with a dialog asking "Are you sure you want to clear this form?" That confirmation transforms reset from an accidental hazard into a deliberate action, which is the appropriate status for a destructive operation. Without confirmation, reset should be considered too dangerous for most forms, and omitted.
There is also a question of whether reset is needed at all, in an age when browsers provide their own ways to clear a form — by refreshing the page, by using autofill to replace values, or by navigating away and back — and when most forms are short enough that re-entering them, if truly desired, is not onerous. For a short contact form, a reset button adds risk without adding value, because the visitor who wants to start over can simply clear the few fields by hand, or refresh the page, and the visitor who does not want to start over gains nothing from the button's presence, except the risk of clicking it accidentally.
<button type="reset">Clear filters</button>
Check: Why is a reset button dangerous next to a submit button?
Section 35 — Handling submit with JavaScript
JavaScript can intercept a form's submission, by listening for its submit event, and can then do something other than, or in addition to, the browser's default submission. The most common pattern is to prevent the default submission, with preventDefault, read the form's data, validate it with custom rules, and send it with a fetch request, without reloading the page, so that the visitor stays on the same page and receives feedback — a success message, an updated list, or an inline error — without a full page load. That pattern underlies many modern interactions: a comment that appears instantly, a search that updates results as you type, a newsletter signup that confirms without navigating away. It is powerful, because it makes forms feel immediate and integrated, rather than like a separate page load, and it allows for richer feedback than a page reload can provide, such as showing a success message in place, or highlighting a specific field without losing the visitor's other input.
Intercepting submission requires care, because it replaces a built-in behaviour that is robust and accessible with a custom behaviour that must be made equally robust and accessible by the author. The first care is to prevent the default only after deciding to handle the submission, and to ensure that, if JavaScript fails, the form still submits by its default mechanism, to its action, by its method, which is why action and method should always be present, even on JavaScript-handled forms, as covered earlier. The second care is to preserve validation: a JavaScript handler that calls preventDefault bypasses the browser's built-in validation only if it does so before validation runs, and in fact, the submit event does not fire at all if built-in validation fails, which means a handler that listens for submit will only run for a form that has already passed built-in validation — a useful guarantee, provided the author understands it, and does not add novalidate unnecessarily, which would remove that guarantee. The third care is feedback: a form submitted by JavaScript must tell the visitor what happened, with a success message that is announced to screen readers, or an error message associated with its field, because a silent JavaScript submission, where the visitor clicks and nothing visibly happens, is a failure, however correctly the data was sent.
A further care is to handle the submit event, not just the button's click event, because a form can be submitted in ways other than clicking its button — by pressing Enter in a text field, for example, or by a screen reader's submit command — and a handler that listens only for clicks will miss those submissions, causing the form to submit by its default mechanism, with a page reload, when the visitor expected the JavaScript behaviour, or vice versa. Listening for submit, on the form element itself, catches all submission methods, uniformly, which is why it is the correct pattern.
<form id="signup">
<label>Email <input name="email" type="email"></label>
<button type="submit">Sign up</button>
</form>
<script>
document.querySelector("#signup").addEventListener("submit", (event) => {
event.preventDefault();
// read data, validate, and send with fetch here
});
</script>
Check: Why listen for the form's submit event rather than the button's click?
Section 36 — Reading form data with FormData
The FormData interface is JavaScript's standard way to read a form's data as a set of name-value pairs, mirroring what the browser would send on a traditional submission. Creating a FormData object from a form element — new FormData(form) — collects every named, enabled field's current value, in the same way the browser collects them for submission, including checkboxes, radio buttons, select menus, and file inputs, with files represented as file objects rather than as text. That collection can then be inspected, modified, or sent, with a fetch request, as the body of a POST request, which the server receives in the same format as a traditional form submission, making FormData the bridge between a traditional form and a JavaScript-handled one. An author who intercepts a form's submit event, as in the previous section, will typically create a FormData object from the form, as the first step in handling its data, because it captures the form's data accurately, without the author having to query each field individually, which is tedious and error-prone, particularly for groups like checkboxes and radio buttons, where the selected values must be determined by checking each option.
FormData's methods allow the author to work with the collected data in useful ways. Get returns the value for a name, getAll returns all values for a name — essential for checkbox groups, where several values share a name, and where get would return only the first — set replaces a value, append adds a value, delete removes a name, and entries, keys, and values allow iteration over the whole set. Those methods make it possible to inspect the data before sending it, to add extra data that is not in the form — a timestamp, for example, or a token — to modify a value, or to log the data for debugging, without altering the form itself. That flexibility is valuable, but it should be used with restraint: modifying form data in JavaScript, before sending it, can confuse the visitor, if the sent data differs from what they entered, and it can introduce bugs, if the modification is incorrect. The cleanest use of FormData is to read the form's data as entered, validate it, and send it as entered, with additions only where they are clearly extra — metadata, not alterations to the visitor's answers — and with the visitor's answers preserved exactly as they gave them.
FormData also handles files correctly, which is a significant advantage over manually collecting field values, because a file input's value, as a string, is only its filename, not its content, while FormData captures the file object itself, with its content, type, and size, ready to be sent as multipart data. That handling makes FormData the correct tool for JavaScript file uploads, where the alternative — reading files manually and constructing a multipart request by hand — is complex and error-prone.
<script>
const form = document.querySelector("#signup");
const data = new FormData(form);
console.log(data.get("email"));
</script>
Check: What does new FormData(form) collect that manually reading inputs might miss?
Section 37 — Keyboard navigation and tab order
Forms are used with keyboards as well as with mice and touch screens, and a form's keyboard navigation — the order in which fields receive focus when the visitor presses Tab — should follow the form's visual and logical order, from first field to last, in the sequence a reader would expect. By default, the tab order follows the document order: the order in which fields appear in the HTML, which, in a well-structured form, is the same as the order in which they appear on the page, top to bottom, left to right in left-to-right languages. That default is correct in almost all cases, and it should be preserved, not overridden, because overriding it with tabindex values — a practice that was once common — creates a tab order that differs from the visual order, which confuses keyboard users, who expect focus to move where their eyes move, and who become disoriented when it jumps elsewhere. The tabindex attribute should be used only in rare, specific cases, such as making a non-focusable element focusable for a custom control, and it should never be used with positive values to reorder a form, because positive tabindex values create a separate tab order that precedes the natural order, and that is almost always a mistake.
Keyboard navigation also includes the behaviours of specific controls. In a text field, Tab moves to the next field, Shift+Tab moves to the previous field, and Enter submits the form, if it has a submit button, as covered earlier. In a select menu, arrow keys move through options, and Enter or Space selects. In a radio group, arrow keys move between options in the group, while Tab moves into and out of the group as a whole, which is an efficient pattern once understood, but which surprises beginners who expect Tab to move between radio buttons individually. In a checkbox, Space toggles it, and Tab moves to the next field. Those behaviours are built into browsers, and they are consistent across sites, which means a visitor who learns them once can use them everywhere, provided authors do not break them with custom controls that behave differently. A custom dropdown, date picker, or slider built with divs and JavaScript, without proper keyboard support, is a common accessibility failure, because it looks like a control but does not behave like one for keyboard users, who cannot operate it at all, or can operate it only with difficulty. The guideline is to prefer native controls, which bring keyboard behaviour for free, and to ensure that any custom control replicates that behaviour fully, including focus, arrow keys, and Enter and Space, before it is used in a form.
A form that is easy to tab through is also easier to test, since every field can be reached and completed without a mouse.
Check: Why should you not use positive tabindex values to reorder a form?
Section 38 — Visible focus styles
When a visitor navigates a form with a keyboard, the currently focused field must be visibly distinguished, so that the visitor knows where they are and what will happen when they type or press a key. Browsers provide a default focus indicator — typically an outline around the focused element — and that indicator is essential for keyboard users, who have no mouse pointer to show their position. Removing that indicator, with CSS that sets outline to none, without providing an alternative, is one of the most damaging accessibility errors in front-end development, because it leaves keyboard users blind to their own position: they press Tab, nothing visibly changes, and they have no idea which field is focused, or whether focus has moved at all. That error is often committed for aesthetic reasons — a designer who finds the default outline ugly, and removes it without replacing it — and it is a case where aesthetics must yield to usability, because a form that cannot be navigated is not beautiful, however clean its unfocused state may look.
The correct practice is to keep a focus indicator, and to style it deliberately, so that it is both visible and harmonious with the design. A focus style can be a coloured outline, a border change, a background change, or a combination, provided it is clearly visible against the field and its surroundings, and provided it meets contrast requirements, so that it is distinguishable for visitors with low vision as well as for those with normal vision. Modern CSS offers a focus-visible pseudo-class, which applies a style only when the browser determines that focus should be visibly indicated — typically for keyboard navigation, but not for mouse clicks, where the visitor already knows where they clicked because they clicked there. Using focus-visible allows an author to provide a strong focus indicator for keyboard users, while avoiding an outline on mouse clicks, where it might be considered visually noisy, which resolves the aesthetic objection without removing the indicator for those who need it. That distinction — between focus that needs to be shown and focus that does not — is a refinement, not a replacement: the indicator must still exist, and must still be strong, for the cases where it is needed.
Focus styles should be tested with a keyboard, in the same way tab order should be tested, by tabbing through a form and checking that each field's focused state is obvious, at a glance, without hunting for a subtle change. A focus style that is a one-pixel change in border colour, on a field that already has a border, is not obvious enough, particularly for visitors with low vision, and it should be strengthened — with a thicker outline, a higher-contrast colour, or an additional background change — until it is unmistakable. Focus styles should also be consistent across a form and across a site, so that a visitor learns what focus looks like once, and recognises it everywhere, rather than encountering a different indicator on every field.
<style>
input:focus-visible { outline: 3px solid #005fcc; outline-offset: 2px; }
</style>
Check: What goes wrong when a focus outline is removed with no replacement?
Section 39 — Mobile keyboards and input modes
On a mobile device, the keyboard that appears when a visitor focuses a field is determined, in part, by the field's type and by its inputmode attribute, and choosing those correctly makes typing on a phone significantly easier. An email field shows a keyboard with an at sign and a dot readily available. A telephone field shows a numeric keypad. A URL field shows a keyboard with shortcuts for dots and slashes. A numeric field shows a numeric keyboard. Those keyboards are not cosmetic; they reduce the number of taps a visitor must make, and they reduce errors, because the keys the visitor needs are prominent, and the keys they do not need are hidden or less accessible. A form that uses type="text" for every field, on a phone, forces the visitor to switch keyboards, to find symbols, and to type numbers on a full keyboard, all of which are slower and more error-prone than using the appropriate keyboard for each field. That is one more reason to use specific input types, as covered earlier: they are not just about validation, they are about the physical act of typing, on the device most visitors now use.
The inputmode attribute refines that keyboard choice, for cases where the type alone does not determine the best keyboard, or where the type's keyboard is not quite right for the data. Inputmode values include "numeric" for numbers, "decimal" for numbers with a decimal point, "tel" for telephone keypads, "email" for email keyboards, "url" for web addresses, and "search" for search keyboards, which may show a search button instead of a return key. Inputmode is particularly useful for fields whose type must be text, for technical reasons, but whose content is numeric — a postcode that contains letters and numbers, for example, where type="number" would be wrong, because postcodes are not quantities, but inputmode="numeric" or a related value might still help, depending on the postcode's format — or for a one-time code field, where inputmode="numeric" shows a numeric keypad, even though the code is an identifier, not a number, and should be a text field with a numeric input mode. Inputmode does not validate, and it does not change what the field accepts; it only suggests a keyboard, which the browser may honour or ignore, depending on the device, but when it is honoured, it makes typing easier, which is a benefit worth claiming with a single attribute.
There are also attributes that control other aspects of mobile typing, and they should be used thoughtfully. Autocapitalize controls whether the browser capitalises the first letter of input, which is appropriate for sentences, but wrong for email addresses, usernames, and codes, where capitalisation would corrupt the value. Autocorrect and spellcheck control whether the browser corrects spelling or checks it, which is helpful for prose, but harmful for names, codes, and addresses, where a "correction" may change a correct value into a wrong one. Those attributes should be set to off for fields where automatic correction could corrupt the data — email, username, code, and similar identifiers — and left on, or set appropriately, for prose fields, where correction helps.
<label for="code">One-time code</label>
<input id="code" name="code" type="text" inputmode="numeric" autocomplete="one-time-code">
Check: What does inputmode control, and what does it not control?
Section 40 — Single checkboxes versus groups
Checkboxes appear in two patterns — singly, and in groups — and the distinction between them matters for structure, for labelling, and for submission. A single checkbox represents one independent choice or agreement: "Send me the newsletter", "I agree to the terms", "Remember me on this device". It has its own name, its own label, and its own meaning, and it is submitted or not, as a single piece of data, depending on whether it is checked. A group of checkboxes represents a set of related choices, from which the visitor may select any number: interests, features, dietary requirements, or any other multi-select question. The group's checkboxes share a name, so that all selected values are submitted together, under that name, and they share a question, which should be marked with a fieldset and legend, so that the group is announced as a group, with its question, rather than as a series of unrelated checkboxes. Confusing the two patterns — treating a group as a series of singles, or a single as a group of one — produces forms that are harder to understand, harder to validate, and harder to process, because their structure does not match their meaning.
The labelling of the two patterns differs accordingly. A single checkbox's label is its entire description: it must say, in full, what checking the box means, because there is no group question to provide context. "I agree to the terms and conditions" is a complete single-checkbox label, because it states the agreement. A group checkbox's label is its individual option: "Vegetarian", "Vegan", "Gluten-free", each a short label for one option, with the group's question — "Dietary requirements" — provided by the legend, not repeated in each label. Repeating the question in each option's label — "Dietary requirement: Vegetarian", "Dietary requirement: Vegan" — is redundant and tedious, particularly for screen reader users, who would hear the question repeated for every option. Conversely, giving a single checkbox a label that is only an option — "Yes", for example, without saying what it agrees to — is incomplete, because there is no group question to supply the missing context, and the visitor must guess from surrounding text what "Yes" means. The rule is to put the question in the legend for groups, and in the label for singles, and to keep each in its place.
Validation also differs between the patterns. A single agreement checkbox is often required, because the form cannot proceed without agreement, and required on a single checkbox means it must be checked, which is a clear, enforceable rule. A group is rarely required as a whole in the same way, because "at least one" is not a rule HTML can express with required alone — required on a group checkbox means that specific box must be checked, not that any box in the group must be checked, which is a different rule, and one that is often misunderstood.
<fieldset>
<legend>Dietary requirements</legend>
<label><input type="checkbox" name="diet" value="vegetarian"> Vegetarian</label>
<label><input type="checkbox" name="diet" value="vegan"> Vegan</label>
</fieldset>
Check: Why does required on one checkbox in a group not mean "choose at least one"?
Section 41 — Select menus with multiple choices
A select element with the multiple attribute allows the visitor to choose several options at once, rather than exactly one, transforming the dropdown into a list box from which multiple items can be selected, typically with Ctrl+click or Cmd+click on a desktop, or with taps on a mobile device, depending on the platform. The size attribute, often used with multiple, controls how many options are visible at once, so that a multiple select with size="5" shows five options in a box, with scrolling for the rest, rather than collapsing into a dropdown that hides its options until opened. That visible list is important, because a multiple select that looks like a single dropdown is confusing: the visitor may not realise multiple choices are possible, or may not know how to make them, particularly the Ctrl+click gesture, which is not obvious and is difficult on a touch screen, where multi-selection in a native picker works differently. A multiple select should therefore be presented as a list, with its multiple nature made clear in its label or hint text — "Choose all that apply" — so that the visitor knows they may select more than one, and how to do so on their device.
Multiple selects submit their data differently from single selects: all selected options' values are submitted, under the same name, as multiple name-value pairs, which the receiving system must handle as a set, not as a single value. That handling varies by server technology — some expect the name to end with brackets, like "diet[]", to indicate an array, while others handle repeated names natively — and the author must know how their receiving system expects multiple values, or the data will be misread, with only the first or last value retained, and the rest discarded silently. That silent discard is a serious data loss bug, and it is prevented by testing the multiple select end to end, with several options chosen, and verifying that all of them arrive at the server, not just one. The same care applies to JavaScript handling with FormData, where getAll, not get, must be used to retrieve all values for a multiple select's name, because get returns only the first. Multiple values are a set, and any code that treats them as a single value will lose data, however correctly the form itself is written.
Whether to use a multiple select at all, versus a checkbox group, is a design decision worth considering carefully. A checkbox group shows all options at once, with a clear, tappable control for each, and it makes multi-selection obvious, without requiring a special gesture, which is why it is generally preferred for multi-select questions, particularly on mobile devices, where checkbox groups are easier to use than multiple selects.
<label for="languages">Languages you speak (choose all that apply)</label>
<select id="languages" name="languages" multiple size="5">
<option value="en">English</option>
<option value="yo">Yoruba</option>
<option value="ig">Igbo</option>
<option value="ha">Hausa</option>
</select>
Check: How does a visitor select multiple options in a multiple select on a desktop?
Section 42 — Marking optional fields
Most forms contain a mix of required and optional fields, and how the optional ones are marked matters as much as how the required ones are marked, because a visitor who cannot tell which fields they may skip will either fill in fields they did not need to, wasting time, or skip fields they should have filled in, causing an error. There are two conventions for marking the mix, and a form should choose one and apply it consistently. The first convention marks required fields, typically with an asterisk and a note saying "Fields marked with * are required", and leaves optional fields unmarked, implying that unmarked fields are optional. The second convention marks optional fields, typically with the word "(optional)" after their labels, and leaves required fields unmarked, implying that unmarked fields are required. The first convention is more common, because most forms have more required than optional fields, so marking the minority — the required ones — is less cluttered than marking the majority. The second convention is clearer when most fields are optional, because marking the minority — the optional ones — is again less cluttered, and because it avoids the ambiguity of an asterisk, which some visitors may not understand, or may not notice.
Whichever convention is chosen, it must be explained, at the top of the form, in plain language, so that a visitor who encounters the first mark understands what it means, without having to guess or to learn it by trial and error. A note saying "* Required" is sufficient for the asterisk convention, though a fuller note — "Fields marked with * must be completed" — is clearer, particularly for visitors who are unfamiliar with the asterisk convention, or who use a screen reader, which may announce the asterisk as "star" without explaining its meaning, unless the form's note provides that explanation in text. For the "(optional)" convention, the marking is self-explanatory, because the word "optional" is itself the explanation, which is one of its advantages: it requires no note, and it is announced clearly by screen readers, as part of the label, without needing interpretation. That self-explanation is why the "(optional)" convention is often recommended by accessibility guidance, even when it is less common in practice: it is the convention that is clearest to the widest range of visitors, without requiring them to learn a symbol.
A further consideration is that optional fields should be genuinely optional, in the form's validation as well as in its marking. A field marked as optional but enforced as required — by a required attribute that was left on by mistake, or by server-side validation that rejects a blank optional field — is a contradiction that frustrates visitors, who were told they could skip the field and then find they cannot submit without it. Conversely, a field marked as required but not enforced is a contradiction in the other direction, which teaches visitors that required marks can be ignored, undermining the marks on genuinely required fields.
<label for="nickname">Nickname (optional)</label>
<input id="nickname" name="nickname" type="text">
Check: Which marking convention is self-explanatory without a note at the top?
Section 43 — Help text placement
Help text — the hints, examples, and explanations that guide a visitor through a field — must be placed where the visitor will find it, at the moment they need it, which is before they enter the field, or while they are entering it, not after they have already failed. The conventional placement is immediately beneath the label and above the field, or immediately beneath the field, and either can work, provided it is consistent across the form, so that a visitor learns where to look for help once, and finds it in the same place for every field. Placement above the field has an advantage for screen reader users, if the help is associated with aria-describedby, because the order of announcement — label, then help, then field — matches the order in which a sighted visitor reads them, top to bottom. Placement beneath the field is also common, and it has the advantage of not pushing the field further from its label, but it means a sighted visitor may enter the field before reading the help beneath it, particularly if the help is long, or if the field is at the bottom of the screen. Whichever placement is chosen, it should be consistent, and the help should be close to its field, not separated by other fields or by large gaps, because help that is far from its field is help that will be missed, or misattributed to the wrong field.
Help text should also be distinguished visually from labels and from error messages, so that a visitor can tell, at a glance, which text is the field's name, which is its help, and which is an error, if one appears. Labels are typically bolder or larger, help text is typically smaller or lighter — though not so light that it fails contrast — and error messages are typically coloured and accompanied by an icon or a border change, in addition to colour, so that they are distinguishable without colour alone. Those visual distinctions matter, because a visitor who cannot tell help from error may think a field has already failed, when it has merely been given a hint, or may miss an error, thinking it is merely help, both of which are confusions that waste time and cause errors. The distinctions should also be programmatic: help text associated with aria-describedby, error text associated with aria-describedby and, where appropriate, with a role that announces it, so that screen reader users receive the same distinctions audibly that sighted visitors receive visually. A form whose help, labels, and errors are visually and programmatically distinct is a form whose visitors always know what kind of text they are reading, and what it means for the field they are about to fill in.
There is a limit to how much help a field should need. A field that requires a paragraph of help to explain what it wants is a field whose label, or whose design, is probably unclear, and whose help is compensating for a deeper problem.
<label for="username">Username</label>
<p id="username-help">Letters and numbers only, 3 to 20 characters.</p>
<input id="username" name="username" type="text" aria-describedby="username-help">
Check: Why should help text placement be consistent across a form?
Section 44 — Logical order of fields
Fields should appear in the order a visitor naturally thinks about them, which is usually the order in which the information occurs to them, or in which they would describe it to another person. A personal details section naturally begins with a name, then an email, then a phone number, because that is the order in which people introduce themselves and give contact details. An address naturally begins with a street, then a city, then a region, then a postcode, then a country, because that is the order in which an address is written and spoken, in most conventions, though address order varies by country, and a form for an international audience should consider the conventions of its visitors, not just of its author. A payment form naturally begins with the cardholder's name, then the card number, then the expiry date, then the security code, because that is the order in which those details appear on a card, and in which a visitor reads them from it. Those natural orders are not arbitrary; they reflect how people think, and a form that follows them feels intuitive, because the visitor never has to hunt for the next field, or wonder why a field appears out of sequence. A form that violates them — asking for a postcode before a street, or for a security code before a card number — feels confusing, even if every field is individually clear, because the sequence itself is wrong.
Logical order also matters for accessibility, because screen reader users and keyboard users encounter fields in document order, which should match visual order, as covered earlier, and which should match logical order, so that all three orders — visual, document, and logical — agree. When they disagree — when a field appears visually first but is last in the document, because CSS has reordered it, or when a field appears logically early but is placed late for layout reasons — keyboard and screen reader users experience a different form from sighted mouse users, and that difference is a source of confusion and error. The remedy is to write fields in logical order in the HTML, and to lay them out in that same order with CSS, without reordering them visually, so that all visitors experience the same sequence, regardless of how they navigate. That discipline is particularly important for multi-column forms, where CSS can easily place a field from the second column before a field from the first, in visual order, while the document order remains first column then second, creating a mismatch that is invisible to the author, who sees only the visual layout, but obvious to a keyboard user, who tabs through the document order and finds focus jumping between columns in an unexpected sequence.
<form>
<label>Name <input name="name"></label>
<label>Email <input name="email" type="email"></label>
<label>Phone <input name="phone" type="tel"></label>
</form>
Check: Why should visual order, document order, and logical order all agree?
Section 45 — Show-password toggles
A password field hides its characters by default, for privacy, so that a visitor typing in a public place, or sharing their screen, does not expose their password to onlookers. That privacy comes at a cost: the visitor cannot see what they have typed, and therefore cannot spot a typo, a wrong keyboard layout, or a caps-lock error, until the password is rejected, at which point they must retype it blind, and may make the same error again. A show-password toggle — a button or checkbox, beside the password field, that switches it between password and text type, revealing the characters when pressed — solves that problem, by giving the visitor control over when their password is visible, so that they can check it in private, and hide it again when they are in public, or when they have finished checking. That control is valuable, and it is now a standard pattern, expected by many visitors, particularly on registration and password-change forms, where a typo in a password can lock the visitor out of a new account, with no easy recovery, because the password they think they set is not the password they actually set.
Implementing a show-password toggle correctly requires attention to its details. The toggle should be a button, with type="button" so that it does not submit the form, and with clear text or an accessible label, such as "Show password", which changes to "Hide password" when the password is shown, so that the visitor always knows what pressing it will do next. The toggle should switch the field's type attribute between password and text, which is a standard JavaScript operation, and it should preserve the field's value and focus when it does so, so that the visitor does not lose their place or their input. The toggle should not be a checkbox without a clear label, because a checkbox labelled only with an eye icon, with no text, is inaccessible to screen reader users who cannot see the icon, and to visitors who do not recognise the icon's meaning. And the toggle should default to hidden — to password type — because privacy is the default that protects the visitor, and visibility is the exception they choose, deliberately, for a moment, to check their input, not a state the form imposes on them from the start, which would expose their password to anyone looking over their shoulder from the moment they begin typing.
There is a broader point about password fields that a toggle does not solve, and that authors should be aware of. Browsers and password managers offer to generate, save, and fill passwords, and those offers should not be blocked by disabling paste in password fields, or by preventing password managers from working, because those restrictions, often imposed in the name of security, actually harm security, by forcing visitors to choose memorable, and therefore weak, passwords, or to reuse passwords across sites, rather than using strong, unique passwords generated and stored by a manager.
<label for="password">Password</label>
<input id="password" name="password" type="password" autocomplete="new-password">
<button type="button" aria-label="Show password">Show</button>
Check: Why should a show-password toggle default to hidden?
Section 46 — Search forms
A search form is a specialised form whose purpose is to ask a question — a search query — and to receive a set of results, and it has conventions that distinguish it from other forms, even though it uses the same elements. Its method is almost always GET, because a search is a request for information, not a change, and because its results should be bookmarkable and shareable, as covered earlier, so that a visitor can save a search, share it, and return to it, with the query visible in the address. Its action points to the search results page, or to the search endpoint, and its main field is typically a search input, type="search", with a name of "q" or "query" or a similar conventional name, which the receiving system expects. Its label is often visually hidden, because a search form's purpose is usually obvious from its context — a box in a header, with a magnifying-glass icon and a search button — but it must still be present programmatically, so that screen reader users hear what the field is for, even when sighted visitors do not need a visible label to understand it. That combination — GET, search type, hidden-but-present label — is the search form's signature, and it is worth recognising as a pattern, because search is one of the most common forms on the web, and getting its conventions right makes it work as visitors expect, across sites.
A search form can also carry a role of search, on the form element itself or on a wrapping element, which marks it as a search landmark for assistive technology, allowing screen reader users to jump directly to the search form, from anywhere on the page, without navigating through other content. That landmark is particularly valuable on pages with many landmarks, where search is a primary way to navigate, and where a visitor who wants to search should not have to hunt for the box. The role should be used once per page, for the site's main search, not for every search-like field on a page — a filter within a list, for example, is not a site search, and marking it as one would confuse the landmark structure, by offering multiple "search" landmarks with no way to distinguish them. The search form's button, typically labelled "Search" or represented by a magnifying-glass icon with an accessible label of "Search", should submit the form, and the form should work without JavaScript, by submitting to its action and loading a results page, even if JavaScript also enhances it with live suggestions or instant results, because a search that only works with scripts is a search that fails when scripts fail, which is a failure for a feature as fundamental as search.
<form action="/search" method="get" role="search">
<label for="site-search">Search this site</label>
<input id="site-search" name="q" type="search">
<button type="submit">Search</button>
</form>
Check: Why should a search form use GET rather than POST?
Section 47 — Consent checkboxes
A consent checkbox is a single checkbox by which a visitor gives explicit permission for something: to receive marketing emails, to have their data processed in a particular way, to agree to terms and conditions, or to opt into a service. Consent checkboxes are legally and ethically significant, in ways that other checkboxes are not, because they represent a visitor's informed agreement, and that agreement must be genuine to be valid. A consent checkbox must therefore be unchecked by default, so that the visitor must actively check it, rather than passively accepting a pre-checked box they may not have noticed, which is not consent but acquiescence, and which is prohibited by privacy regulations in many jurisdictions, and by the principle of informed agreement generally. A pre-checked consent box is a dark pattern — a design that tricks a visitor into agreeing to something they did not intend — and it should never be used, however much it might increase signups, because signups obtained by trickery are not consent, and they damage trust when the visitor discovers what they have agreed to, which they will, when the emails arrive or the data is used.
A consent checkbox's label must also be clear, specific, and honest about what is being consented to, in plain language, without burying the substance in legal jargon or in a linked document the visitor is unlikely to read. "Send me marketing emails about new products" is clear consent for marketing emails. "I agree to the terms and conditions" is clear only if the terms are linked, and the visitor is given a real opportunity to read them, before checking the box, and if the terms themselves are understandable, which is a broader question about the terms' writing, not just the checkbox's label. "I consent to data processing" is vague, because it does not say what data, for what purpose, or by whom, and a visitor who checks it has not given informed consent to anything specific, however the form may record it. The label should name the action, the data, and the purpose, in as few words as clarity allows, and it should be accompanied, where the consent is complex, by a short explanation, or a link to a fuller explanation, that the visitor can read before deciding. Consent is a decision, and a decision requires information, which the form must provide, at the moment of decision, not after it.
Consent checkboxes should also be separate, where the consents are separate. A single checkbox that bundles multiple consents — marketing emails, data sharing with third parties, and terms agreement, all in one box — forces the visitor to accept all or none, which is not genuine consent to each, and which is prohibited by regulations that require consent to be granular, so that a visitor can agree to one thing and decline another, without penalty. Each distinct consent should have its own checkbox, with its own label, so that the visitor can choose independently, and so that the form's record shows, for each consent, whether it was given or withheld, which is essential for the receiving system to honour the visitor's choices correctly.
<label><input type="checkbox" name="marketing" value="yes"> Send me marketing emails about new products</label>
Check: Why must a consent checkbox never be pre-checked?
Section 48 — Testing forms
Testing a form means using it as a visitor would, in all the ways a visitor might, and checking that it behaves correctly in each case, not merely that it looks correct on screen. The most basic test is to complete the form successfully, with valid data, and to verify that the submission arrives correctly, with all fields present, under their correct names, and with their values intact, including special characters, spaces, and non-English text, which are often mishandled by receiving systems that assume simple, English-only input. The next test is to submit the form with each required field blank, in turn, and to verify that submission is refused, with a clear message for that field, and that the visitor's other input is preserved, so that they can correct the error without re-entering everything. The next test is to submit with invalid data — a malformed email, a number outside its range, a value that does not match its pattern — and to verify that each invalid value is rejected, with a message that explains the problem and how to fix it, in plain language, not in technical jargon. Those three tests — valid, missing, invalid — cover the basic paths through a form, and a form that passes all three, for every field, is a form whose validation is sound, at least at the client level.
Further tests cover the ways visitors actually use forms, which are more varied than the basic paths suggest. Test the form with a keyboard only, as covered earlier, and verify that tab order is logical, that focus is visible, and that the form can be completed and submitted without a mouse. Test it with a screen reader, if one is available, and verify that every field is announced with its label, that hints and errors are announced, and that groups are announced as groups, with their legends. Test it on a mobile device, and verify that the correct keyboards appear for each field, that the form is usable with a keyboard covering half the screen, and that its buttons are large enough to tap without error. Test it with JavaScript disabled, and verify that it still submits by its default mechanism, to its action, with its data intact, because a form that only works with scripts is a form that fails for visitors whose scripts fail, for whatever reason. Test it with a slow network, and verify that submitting twice, by double-clicking the submit button, does not create duplicate submissions, which is a common bug that should be prevented by disabling the button on first submit, or by handling duplicates server-side. Each of those tests corresponds to a real visitor, in a real situation, and a form that passes them all is a form that serves real visitors, not just the author's test case.
Check: Why test a form with JavaScript disabled, even if it uses JavaScript?
Section 49 — Common form mistakes
Forms fail in predictable ways, and learning those failures in advance is the fastest way to avoid them, because most form bugs are not novel; they are variations on a small set of mistakes that every developer makes at least once. The first is a field without a name, which submits nothing, however well it is labelled and filled in, and which is discovered only when the receiving system reports a missing value, or when a visitor complains that their message arrived incomplete. The second is a field without a label, or with a label that is not associated, which looks fine on screen but is unnamed for screen reader users, and which is discovered only by testing with a screen reader, or by clicking the label and noticing that the field does not receive focus. The third is a placeholder used as a label, which disappears when the visitor types, leaving the field unnamed, and which is discovered by typing in the field and then forgetting what it was for, which is an experience every visitor has had, and which every form author should have learned from. Those three — no name, no label, placeholder as label — are the most common form mistakes, and they are all prevented by a simple checklist, applied to every field: does it have a name, does it have a label, is that label associated, and is its placeholder, if any, only a hint?
Further mistakes involve types, validation, and buttons. Using type="text" for every field, instead of the specific type for email, telephone, number, date, and other data, loses validation, keyboards, and meaning, for no benefit, and is prevented by asking, for each field, whether a specific type exists for its data. Using type="number" for a phone number, which breaks numbers with plus signs, leading zeros, and spaces, is a specific instance of that mistake, and it is prevented by remembering that a phone number is an identifier, not a quantity. Forgetting required on a field that is genuinely mandatory, or adding required to a field that is genuinely optional, produces forms that either accept incomplete data or reject complete visitors, both of which are failures of the form's contract with its visitor, and both of which are prevented by deciding, field by field, whether the form can be acted on without that field's data. Using a button without a type, inside a form, which submits unexpectedly, is a mistake prevented by always giving buttons an explicit type. And using a reset button beside a submit button, without confirmation, is a mistake prevented by omitting reset, or by confirming it, as covered earlier. Each of those mistakes has a simple prevention, and the prevention is cheaper than the cure, which is why a checklist, applied before a form goes live, is one of the most valuable tools in form development.
Check: Name three common form mistakes from this section.
Section 50 — A complete form, put together
This chapter has introduced forms piece by piece: the form element, its action and method, its fields and their types, its labels and validation, its groups and its accessibility, and its handling in JavaScript. Now it is time to put those pieces together into a complete form, and to see how they work as a system, rather than as isolated attributes. A complete form begins with a form element, with an action and a method that describe where its data goes and how, and with a method chosen by whether the form changes something or merely asks for something. It contains fields in logical order, grouped by fieldset where they answer a common question, each field with a name for submission, an id for labelling, a label that names it clearly, a type that matches its data, and validation — required, pattern, min, max, or a specific type's built-in check — that matches the data's real constraints, described in visible hint text where the constraint is not obvious. It contains a submit button whose text describes the action it performs, and it contains no reset button unless a genuine need for one has been identified, and no button without an explicit type, anywhere. That is the complete form's structure, and a form built to that structure is already usable, accessible, and robust, before any styling or scripting is added.
Styling and scripting, when added, should enhance that structure, not replace it. Styling should make the form's fields, labels, hints, and errors visually distinct, its focus states obvious, and its layout clear on any screen, without reordering its fields visually away from their document order, and without removing focus indicators or hiding labels for aesthetic reasons. Scripting should enhance the form's behaviour — with live validation messages that supplement built-in validation, with a show-password toggle, with a dynamic field that appears when an option is chosen, or with a JavaScript submission that confirms without a page reload — while preserving the form's default behaviour as a fallback, so that the form still works when scripts fail, and so that its data is still submitted, by its action and method, to a server that validates it again. That layering — structure first, styling second, scripting third — is the same layering that runs through all of front-end development, and forms are where it matters most, because a form's failure is not a visual failure, but a functional one: a visitor who cannot submit a form cannot complete their task, however beautiful the form may look, and a form that submits invalid data cannot be trusted, however smoothly its scripts may run.
<form action="/contact" method="post">
<label for="name">Name *</label>
<input id="name" name="name" type="text" required>
<label for="email">Email address *</label>
<input id="email" name="email" type="email" required>
<label for="message">Message</label>
<textarea id="message" name="message" rows="5"></textarea>
<button type="submit">Send message</button>
</form>
Check: In your own words, what makes a complete form trustworthy?
Practical task
Build a complete contact form, in HTML, with a small amount of JavaScript only for a show-password toggle if you include a password field — otherwise HTML alone is enough. It must have: a form with an action and a method, a fieldset with a legend for personal details, a text field, an email field, a telephone field, a select menu, a radio group, a checkbox group, a textarea, a required field marked as required, a patterned field with its format described in visible text, and a submit button whose text describes its action. Then test it thoroughly: submit it with valid data, with a required field blank, and with an invalid email; complete it using only the keyboard; and view it with images disabled or with a screen reader if you have one. Fix every problem your testing reveals before you consider the task done.
Chapter questions
- Why must every field have both a label and a name, and what different jobs do they do?
- Write the HTML for a radio group of three delivery options, with a fieldset, a legend, and correctly associated labels.
- A form's file upload submits only the file's name, not its content. What is missing, and why?
- Explain, in plain words, the difference between disabled and readonly.
- A visitor submits a form and loses all their input when one field fails validation. What went wrong, and how should the form have behaved?