webdev.complete
📝 HTML — Forms, Media, and Accessibility
ðŸ§ąFoundations
Lesson 7 of 117
35 min

Forms Deep Dive

Every input, validation, labels, and the modern <dialog>.

Forms are how the web gets data from users instead of just showing things tothem. They're also the area where new developers reach hardest for JavaScript when they shouldn't. Modern HTML has so much built-in form behavior that you can ship a complete login flow with zero JS and never sacrifice UX.

The form element

html
<form action="/signup" method="post">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required />

  <button type="submit">Sign up</button>
</form>

Two attributes you'll set on every <form>: action (where the data goes) and method (usually get or post). With nothing else, this form actually works. Submit it and the browser POSTs to /signup with the email field.

Always name your inputs
Without a name attribute, an input's value is not submitted with the form. This is the most common forgotten-detail in beginner forms.

The input types you should know

The type attribute does a lot more than change the icon. It changes the mobile keyboard, native validation, and how assistive tech describes the field.

  • type="text" - generic single-line. The default.
  • type="email" - validates basic email shape, shows the email keyboard on mobile (with the @ key).
  • type="password" - masks input, opts out of autocomplete by default.
  • type="tel"- phone numbers. Doesn't validate format (formats vary by country) but shows the numeric keypad on mobile.
  • type="number" - numeric only. Use inputmode="numeric" on text inputs if you want a number keypad without the spinner buttons.
  • type="date" - opens a native date picker. type="datetime-local", type="time", and type="month" are siblings.
  • type="range" - a slider.
  • type="color" - a native color picker.
  • type="file" - file upload. Add accept="image/*" or accept=".pdf" to restrict.
  • type="checkbox" - independent on/off toggles.
  • type="radio" - mutually exclusive options. Share a name across siblings to group them.

Labels: not optional

Every input needs a label. There are two ways to associate them:

html
<!-- Method 1: for + id -->
<label for="email">Email</label>
<input id="email" name="email" type="email" />

<!-- Method 2: wrap the input -->
<label>
  Email
  <input name="email" type="email" />
</label>

Both work. Method 1 is more flexible for layout (you can position the label anywhere). Note: in React/JSX, for becomes htmlFor because for is a reserved word.

Placeholder is not a label
A placeholder vanishes when the user types, which is terrible for accessibility and for people who get distracted mid-form. Use a real label every time. Placeholders are for examples ("e.g. ada@example.com"), not titles.

Native validation

HTML has built-in validation. You don't need JS to require a field, check a pattern, or set a length.

  • required - must have a value.
  • minlength / maxlength - character bounds.
  • min / max - numeric bounds for number, date, etc.
  • pattern - a regex the value must match.
  • type="email" / type="url" - type-specific built-in validation.
html
<label for="username">Username</label>
<input
  id="username"
  name="username"
  type="text"
  required
  minlength="3"
  maxlength="20"
  pattern="[a-zA-Z0-9_]+"
  title="Letters, numbers, and underscores only"
/>

On submit, the browser blocks the form and shows a tooltip pointing at the offending field. Style invalid fields with the :invalid CSS pseudo-class.

Beyond input: select, textarea, datalist

html
<!-- Dropdown -->
<label for="role">Role</label>
<select id="role" name="role">
  <option value="">Pick one</option>
  <option value="admin">Admin</option>
  <option value="member">Member</option>
</select>

<!-- Multi-line text -->
<label for="bio">Bio</label>
<textarea id="bio" name="bio" rows="4" maxlength="500"></textarea>

<!-- Autocomplete suggestions on a regular input -->
<label for="city">City</label>
<input id="city" name="city" list="cities" />
<datalist id="cities">
  <option value="Tokyo" />
  <option value="Paris" />
  <option value="Lagos" />
  <option value="Sao Paulo" />
</datalist>

<datalist> is underused: it gives you native autocomplete suggestions without the user being forced to pick from the list. They can also type their own value.

The modern dialog element

<dialog> is a native modal. No library needed. It handles focus trapping, the backdrop, and ESC-to-close.

html
<button onclick="myDialog.showModal()">Open</button>

<dialog id="myDialog">
  <form method="dialog">
    <p>Are you sure?</p>
    <button value="cancel">Cancel</button>
    <button value="confirm">Yes, delete</button>
  </form>
</dialog>

A form with method="dialog" inside a dialog automatically closes it on submit and returns which button was clicked. Total magic, zero JS.

The popover API

Newer than <dialog> and useful for tooltips, menus, and any non-modal floating UI:

html
<button popovertarget="menu">Show menu</button>
<div id="menu" popover>
  <p>I&apos;m a popover. Click anywhere to dismiss.</p>
</div>

Add popover to any element to make it a popover. Wire it up with popovertarget on a button. Light-dismiss (click outside to close), top-layer rendering, and focus management are all handled for you.

Try it: a real signup form

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Signup</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <form>
      <h2>Create your account</h2>

      <label for="name">Full name</label>
      <input id="name" name="name" type="text" required minlength="2" />

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

      <label for="password">Password</label>
      <input id="password" name="password" type="password" required minlength="8" />

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

      <label>
        <input type="checkbox" name="terms" required />
        I agree to the terms
      </label>

      <button type="submit">Sign up</button>
    </form>
  </body>
</html>

Try submitting it empty. Notice the browser refuses and points to the first empty field. Try entering "a" for the name - it tells you the minimum length. That's zero lines of JS validation.

Quick quiz

Quiz1 / 4

An input has no name attribute. What happens on submit?

Recap

  • <form> takes action and method. Every input needs a name.
  • Pick type deliberately - it changes the mobile keyboard, native validation, and accessibility.
  • Use real <label>s. Placeholder is for examples, not titles.
  • Native validation: required, minlength, maxlength, pattern, min, max.
  • <select>, <textarea>, <datalist> cover dropdowns, multiline, and autocomplete.
  • <dialog> and the popover API give you modals and floating UI without libraries.
Built with Next.js, Tailwind & Sandpack.
Learn. Build. Ship.