Forms & Inputs

Building forms with labels, input types, select and textarea, and built-in HTML5 validation attributes.

The <form> element

A <form> collects user input and submits it to a server:

HTML
<form action="/subscribe" method="POST">
    <label for="email">Email address</label>
    <input type="email" id="email" name="email" required>
    <button type="submit">Subscribe</button>
</form>
  • action — the URL the form data is sent to.
  • methodGET appends data to the URL as a query string (fine for searches, bad for sensitive data); POST sends data in the request body (used for anything that creates/changes data, or contains sensitive information).
  • Every input needs a name attribute — it's the key used when the data is submitted (email=user@example.com). Without name, the field's value is never sent.

Labels: <label for>

Every input needs an associated <label>. This isn't optional politeness — it's a core accessibility requirement, and it also gives sighted users a bigger, easier click/tap target:

HTML
<label for="username">Username</label>
<input type="text" id="username" name="username">

The for attribute must match the input's id exactly. Alternatively, wrap the input inside the label to associate them implicitly:

HTML
<label>
    Username
    <input type="text" name="username">
</label>

A screen reader announces the label text whenever the input receives focus. Without it, a user tabbing through the form hears nothing but "edit text" — no indication of what to type.

Input types

The type attribute on <input> does a lot of work: it changes the on-screen keyboard on mobile, enables built-in browser validation, and sometimes swaps in a completely different UI widget.

HTML
<input type="text" name="fullname" placeholder="Jane Doe">
<input type="email" name="email" placeholder="jane@example.com">
<input type="password" name="password">
<input type="number" name="age" min="0" max="120">
<input type="date" name="birthday">
<input type="tel" name="phone">
<input type="url" name="website">
<input type="checkbox" name="subscribe" checked>
<input type="radio" name="plan" value="free">
<input type="radio" name="plan" value="pro">
<input type="range" name="volume" min="0" max="100">
<input type="color" name="theme-color">
<input type="file" name="avatar" accept="image/*">
<input type="search" name="query">
Type Use case Notable behavior
email Email addresses Mobile keyboards show @; browser validates basic email shape on submit
number Numeric input Spinner arrows; respects min/max/step
date Calendar dates Native date-picker UI, no JS library needed
checkbox Independent on/off toggles Multiple can be checked at once
radio Mutually exclusive choice Inputs sharing the same name form one group — only one can be selected
range A value within a bounded range Renders as a slider
tel Phone numbers Mobile numeric keyboard; no automatic format validation (phone formats vary too much globally)
url Web addresses Browser validates it looks like a URL
file File uploads accept restricts the file picker (e.g. accept="image/*")
search Search boxes Some browsers show a built-in "clear" (×) button

Checkboxes and radios with the same name are how a radio group works — the browser enforces that only one radio in a group can be checked, purely from markup, no JavaScript required.

Validation attributes

HTML5 ships built-in client-side validation — no JavaScript needed for the common cases:

HTML
<form>
    <label for="username">Username (required, 3-16 characters)</label>
    <input type="text" id="username" name="username" required minlength="3" maxlength="16">

    <label for="age">Age (18+)</label>
    <input type="number" id="age" name="age" min="18" max="120" required>

    <label for="zip">ZIP code (5 digits)</label>
    <input type="text" id="zip" name="zip" pattern="[0-9]{5}" title="Enter a 5-digit ZIP code">

    <button type="submit">Sign up</button>
</form>
Attribute Effect
required Field must be filled before the form submits
minlength / maxlength Bounds on text length
min / max Bounds on numeric or date values
pattern A regular expression the value must match
step Increment granularity for number/range/date inputs

When validation fails, the browser blocks submission and shows a native tooltip pointing at the offending field — automatically, with zero JavaScript. This is called constraint validation, and it's always worth using as the first line of defense, with server-side validation as the real source of truth (client-side validation can always be bypassed).

<select> and <textarea>

<select> renders a native dropdown:

HTML
<label for="country">Country</label>
<select id="country" name="country">
    <option value="">Select a country</option>
    <option value="us">United States</option>
    <option value="ca">Canada</option>
    <option value="uk">United Kingdom</option>
</select>

Add multiple to allow selecting more than one option, or group related options with <optgroup>.

<textarea> is for multi-line free text — note it's a paired tag, not a void element, and its content goes between the tags rather than in a value attribute:

HTML
<label for="bio">Bio</label>
<textarea id="bio" name="bio" rows="4" cols="40" placeholder="Tell us about yourself"></textarea>

A complete example

HTML
<form action="/register" method="POST">
    <div>
        <label for="name">Full name</label>
        <input type="text" id="name" name="name" required>
    </div>

    <div>
        <label for="email">Email</label>
        <input type="email" id="email" name="email" required>
    </div>

    <div>
        <label for="plan">Plan</label>
        <select id="plan" name="plan">
            <option value="free">Free</option>
            <option value="pro">Pro</option>
        </select>
    </div>

    <div>
        <label for="bio">Bio</label>
        <textarea id="bio" name="bio" rows="3"></textarea>
    </div>

    <div>
        <input type="checkbox" id="terms" name="terms" required>
        <label for="terms">I agree to the terms of service</label>
    </div>

    <button type="submit">Create account</button>
</form>

Common mistakes

  • Omitting name on an input — the field's value simply won't be included when the form submits, even though it looks fine visually.
  • Using placeholder as a replacement for <label> — placeholder text disappears the moment the user starts typing, and isn't reliably announced by all screen readers the way a real label is.
  • Relying only on client-side (required, pattern) validation and skipping server-side validation — client-side checks are a UX convenience, never a security boundary, since they can be bypassed by anyone sending a raw HTTP request.
  • Forgetting type="button" on non-submit buttons inside a <form> — a plain <button> inside a form defaults to type="submit" and will submit the form when clicked, which is a common source of "why did my page reload" bugs.