π·οΈ Form Controls and Labels
The <input> element can't do everything. This lesson completes your form toolkit with the specialized controls β dropdowns, multi-line text, grouped fieldsets, autocomplete datalists, live outputs, and progress bars β and grounds all of them in the single most important accessibility habit: labeling every control correctly.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Associate labels with controls using both explicit (
for/id) and implicit nesting - Build
<select>menus with<optgroup>andmultiple, and multi-line<textarea>fields - Group related controls with
<fieldset>and<legend> - Use
<datalist>,<output>,<progress>, and<meter>where they fit - Apply ARIA attributes (
aria-describedby,aria-invalid) for help text and error messaging
Estimated Time: 35β45 minutes β’ Difficulty: BeginnerβIntermediate
Hands-on: Build an accessible survey form using every control covered here.
In This Lesson
Beyond the Input
While <input> covers a lot, HTML provides purpose-built elements for the jobs it can't do well: a compact dropdown of many options, a roomy multi-line text box, a semantic group of related fields, an autocomplete list, and read-outs of progress. Knowing which element fits which question is what separates a serviceable form from a genuinely pleasant one.
π‘ A useful analogy: Form controls are the different question types on a well-designed survey. Some need a short written answer (input), some pick one from a long list (select), some invite a paragraph (textarea), and some just report a status back to you (progress/meter). Choosing the right question type is half of good form design.
Labels: The Foundation
Before any fancy control, get labels right β they carry more weight than any other single habit in form building.
- Usability: a label tells the user exactly what a control is for.
- Accessibility: screen readers announce the label when the control gains focus.
- Bigger hit target: clicking the label focuses (or toggles) the control β a real gift on touchscreens.
- Structure: labels give the form visual and semantic organization.
Analogy: A form without labels is a building with no signs on the doors β technically usable, but everyone gets lost.
Explicit labeling (preferred)
Match the label's for to the control's id. This works even when the label and control aren't next to each other in the markup.
<label for="email">Email address</label>
<input type="email" id="email" name="email">
Implicit labeling
Wrap the control inside the label. No for/id needed, but you lose some styling and assistive-tech flexibility.
<label>
Email address
<input type="email" name="email">
</label>
π Which to use?
Prefer explicit labeling. It is the most reliably supported by assistive technologies and gives you full freedom to position and style the label independently of the control.
Select & Textarea
The <select> dropdown
A space-saving menu of predefined options. Include a placeholder-style first option so the field starts neutral.
<label for="country">Country</label>
<select id="country" name="country">
<option value="">β Please choose β</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
</select>
Grouping options with <optgroup>
<label for="car">Car model</label>
<select id="car" name="car_model">
<optgroup label="Sedans">
<option value="accord">Honda Accord</option>
<option value="camry">Toyota Camry</option>
</optgroup>
<optgroup label="SUVs">
<option value="crv">Honda CR-V</option>
<option value="rav4">Toyota RAV4</option>
</optgroup>
</select>
Multiple selections
Add multiple (and usually size) to let users pick several. Name it with [] so server frameworks treat it as an array.
<label for="langs">Languages you know</label>
<select id="langs" name="languages[]" multiple size="5">
<option value="html">HTML</option>
<option value="css">CSS</option>
<option value="js">JavaScript</option>
<option value="python">Python</option>
</select>
The <textarea>
A multi-line text box for comments, reviews, and messages. Unlike <input>, it needs a closing tag, and any initial value goes between the tags β so keep it empty to start with a blank field.
<label for="comments">Comments</label>
<textarea id="comments" name="comments" rows="5"
placeholder="Share your thoughts..."></textarea>
Useful attributes: rows (visible lines), maxlength/minlength (character limits), and wrap (soft, hard, or off).
The <button> element
Prefer <button> over <input type="submit"> β it can hold markup and is far easier to style. Always set its type explicitly, because an unset button defaults to submit and can fire your form unexpectedly.
<button type="submit">Save changes</button>
<button type="button" id="preview">Preview</button>
<button type="reset">Clear</button>
Grouping: Fieldset & Legend
The <fieldset> element groups related controls, and <legend> gives that group a caption. This is both a visual convenience and an accessibility win: screen readers announce the legend as context for every control inside the group β essential for radio-button sets, where a lone "Express" makes no sense without "Shipping method."
<fieldset>
<legend>Delivery address</legend>
<label for="street">Street</label>
<input type="text" id="street" name="street" required>
<label for="city">City</label>
<input type="text" id="city" name="city" required>
<label for="zip">ZIP code</label>
<input type="text" id="zip" name="zip" pattern="[0-9]{5}" required>
</fieldset>
Analogy: If a form is a book, fieldsets are its chapters β each <legend> is a chapter title that tells the reader what this section is about.
β Always group a radio set
A set of radio buttons should live inside a <fieldset> with a descriptive <legend>. Without it, assistive tech reads each option in isolation and the user loses the question the options answer.
Datalist, Output, Progress & Meter
<datalist> β suggestions without restriction
A datalist attaches a set of suggested values to a text input via the list attribute. Users can pick a suggestion or type something entirely different.
<label for="browser">Favorite browser</label>
<input list="browsers" id="browser" name="browser">
<datalist id="browsers">
<option value="Firefox">
<option value="Chrome">
<option value="Safari">
<option value="Edge">
</datalist>
Analogy: A datalist is a helpful assistant who suggests common answers but never stops you from writing your own.
<output> β showing a calculated result
A semantic place to display the result of a calculation, often driven by the form's input event.
<form oninput="sum.value = Number(a.value) + Number(b.value)">
<label for="a">A</label>
<input type="number" id="a" name="a" value="0">
<label for="b">B</label>
<input type="number" id="b" name="b" value="0">
<output name="sum" for="a b">0</output>
</form>
<progress> vs <meter>
Both are read-outs, but they mean different things. <progress> shows how far along a task is (an upload finishing). <meter> shows a measurement within a known range (disk usage, a rating), and can flag low/high zones.
<label for="upload">Upload progress</label>
<progress id="upload" value="70" max="100">70%</progress>
<label for="disk">Disk usage</label>
<meter id="disk" value="0.6" min="0" max="1"
low="0.3" high="0.7" optimum="0.5">60%</meter>
π Progress or meter?
Ask: "Is this moving toward completion?" If yes, use <progress>. If instead it's a static gauge of a value within a range β a fuel level, a score, a capacity β use <meter>.
Accessibility & ARIA
Correct labels get you most of the way. A few ARIA attributes handle the rest β help text and error states that native HTML can't express on its own.
Associating help text with aria-describedby
Point a control at descriptive text so a screen reader reads it right after the label.
<label for="pw">Password</label>
<input type="password" id="pw" name="password"
aria-describedby="pw-help" required>
<p id="pw-help">At least 8 characters, with a number and an uppercase letter.</p>
Signaling errors with aria-invalid
Mark a failing field and connect it to its error message. Never rely on color alone β pair it with text or an icon.
<label for="user">Username</label>
<input type="text" id="user" name="username"
aria-invalid="true" aria-describedby="user-error">
<p id="user-error" class="error-message">
<span aria-hidden="true">β οΈ</span> Username must be at least 5 characters.
</p>
β οΈ Common accessibility slips
- Using
placeholderas the only label β it disappears the moment the user types. - Indicating errors with a red border and nothing else β invisible to colorblind users.
- Radio/checkbox groups with no
<fieldset>/<legend>to give them context.
Worked Example: A Survey Form
This survey pulls the whole lesson together β labels, fieldsets, a grouped select, a radio set, a textarea, and a live meter driven by a range.
<form action="/submit-survey" method="post">
<fieldset>
<legend>About you</legend>
<label for="name">Full name</label>
<input type="text" id="name" name="name">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<label for="age">Age group</label>
<select id="age" name="age_group">
<option value="">β Select β</option>
<option value="18-24">18β24</option>
<option value="25-34">25β34</option>
<option value="35plus">35 and above</option>
</select>
</fieldset>
<fieldset>
<legend>How likely are you to recommend us?</legend>
<label for="score">Score (0β10)</label>
<input type="range" id="score" name="score" min="0" max="10" value="7"
oninput="scoreOut.value = this.value">
<output id="scoreOut" for="score">7</output>
</fieldset>
<fieldset>
<legend>Preferred contact method</legend>
<input type="radio" id="c-email" name="contact" value="email" checked>
<label for="c-email">Email</label>
<input type="radio" id="c-phone" name="contact" value="phone">
<label for="c-phone">Phone</label>
<input type="radio" id="c-none" name="contact" value="none">
<label for="c-none">Don't contact me</label>
</fieldset>
<label for="notes">Anything else?</label>
<textarea id="notes" name="notes" rows="4"
placeholder="Optional feedback..."></textarea>
<button type="submit">Submit survey</button>
</form>
What makes this form accessible
Every control has an explicit label, each group of related fields sits in a <fieldset> with a meaningful <legend>, and the <output> gives sighted users live feedback on the slider. A keyboard user can tab through it top to bottom with full context at every step.
Hands-on Exercise
ποΈ Build an Accessible Feedback Form
Objective: Use every category of control from this lesson in one well-labeled, accessible form.
Requirements:
- A
<select>with an<optgroup>for choosing a product category. - A radio-button group inside a
<fieldset>/<legend>for a 1β5 rating. - A text input wired to a
<datalist>of suggested tags. - A
<textarea>for comments with a helpfulplaceholder. - Help text on one field connected via
aria-describedby. - A proper
<button type="submit">.
Test it with the keyboard only: you should reach and operate every control with Tab, arrow keys, and Space/Enter.
π‘ Hint
Link each label with for matching the control's id. Give the datalist an id and reference it from the input's list attribute. For the rating, all radios must share one name so only one can be chosen.
β Sample solution
<form action="/feedback" method="post">
<label for="category">Product category</label>
<select id="category" name="category">
<optgroup label="Hardware">
<option value="laptop">Laptop</option>
<option value="phone">Phone</option>
</optgroup>
<optgroup label="Software">
<option value="app">Mobile app</option>
<option value="web">Web app</option>
</optgroup>
</select>
<fieldset>
<legend>Your rating</legend>
<input type="radio" id="r5" name="rating" value="5">
<label for="r5">5 β Excellent</label>
<input type="radio" id="r3" name="rating" value="3" checked>
<label for="r3">3 β Okay</label>
<input type="radio" id="r1" name="rating" value="1">
<label for="r1">1 β Poor</label>
</fieldset>
<label for="tag">Tag</label>
<input list="tags" id="tag" name="tag" aria-describedby="tag-help">
<datalist id="tags">
<option value="bug">
<option value="idea">
<option value="praise">
</datalist>
<p id="tag-help">Pick a suggestion or type your own.</p>
<label for="comments">Comments</label>
<textarea id="comments" name="comments" rows="4"
placeholder="Tell us more..."></textarea>
<button type="submit">Send feedback</button>
</form>
π― Quick Quiz
Question 1: Which labeling approach is generally preferred for the broadest assistive-technology support and styling freedom?
Question 2: You need to show how far along a file upload is. Which element is semantically correct?
Question 3: Why should a group of radio buttons be wrapped in a <fieldset> with a <legend>?
Best Practices
β Do
- Label every control explicitly, and let clicking the label activate it.
- Group related controls in
<fieldset>with a descriptive<legend>. - Set
typeon every<button>to avoid accidental submits. - Pair errors with text or icons, not color alone, and wire them up with
aria-describedby. - Use
<optgroup>to make long dropdowns scannable.
β Don't
- Don't use
placeholderas a label β it vanishes and isn't reliably announced. - Don't put text inside a starting-empty
<textarea>β the content between the tags becomes its value. - Don't scatter ungrouped radios without a fieldset β screen-reader users lose the question.
- Don't confuse
<progress>and<meter>β task versus measurement.
Summary & Quiz
π Key Takeaways
- Labels are the foundation: prefer explicit
for/idpairing on every control. <select>(with<optgroup>andmultiple) and<textarea>cover options and long-form text.<fieldset>+<legend>group related controls and give radio/checkbox sets essential context.<datalist>,<output>,<progress>, and<meter>each fit a specific job.- ARIA (
aria-describedby,aria-invalid) adds accessible help text and error states.
π Further Reading
π What's Next?
You can now build a complete, accessible form from every control HTML offers. Next we make it bulletproof: HTML5 Form Validation covers the built-in constraint attributes, the Constraint Validation API, and crafting clear, friendly error messages.
π Nice work!
Your forms are now organized, accessible, and ready for real users β whatever device or assistive tech they bring.