TechLead

Forms, Validation, and UX

Input types, constraints, and accessible form design

HTML forms collect user input and submit it to a server. Modern HTML provides rich input types and built-in validation that catches errors before data leaves the browser, reducing server load and improving user experience.

Form Structure

<form action="/signup" method="post" novalidate>
  <div>
    <label for="email">Email address</label>
    <input
      id="email"
      type="email"
      name="email"
      required
      autocomplete="email"
      placeholder="you@example.com"
    >
    <span class="error" aria-live="polite"></span>
  </div>

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

Input Types

Using the correct type triggers appropriate mobile keyboards and enables built-in validation.

<input type="email"    name="email">     <!-- validates email format -->
<input type="url"      name="website">   <!-- validates URL format -->
<input type="tel"      name="phone">     <!-- numeric keypad on mobile -->
<input type="number"   name="age" min="18" max="120">
<input type="date"     name="dob">
<input type="password" name="pwd" minlength="8">
<input type="search"   name="q">        <!-- shows clear button -->
<input type="range"    name="vol" min="0" max="100" step="5">
<input type="color"    name="brand">
<input type="file"     name="avatar" accept="image/*" multiple>
<input type="checkbox" name="agree" required>
<input type="radio"    name="plan" value="free">

Built-in Validation Attributes

<input type="text"
  required                          <!-- must not be empty -->
  minlength="3" maxlength="50"      <!-- character count -->
  pattern="[A-Za-z]+"              <!-- regex constraint -->
  title="Letters only"              <!-- shown in native error bubble -->
>

<input type="number" min="1" max="99" step="1">

Custom Validation with the Constraint Validation API

Use novalidate on the form and implement your own validation loop for consistent cross-browser styling and better error messages.

const form = document.querySelector('form');

form.addEventListener('submit', (e) => {
  e.preventDefault();
  let valid = true;

  form.querySelectorAll('[required]').forEach(input => {
    const errorEl = input.nextElementSibling;
    if (!input.validity.valid) {
      valid = false;
      errorEl.textContent = getErrorMessage(input);
      input.setAttribute('aria-invalid', 'true');
    } else {
      errorEl.textContent = '';
      input.removeAttribute('aria-invalid');
    }
  });

  if (valid) form.submit();
});

function getErrorMessage(input) {
  if (input.validity.valueMissing)  return 'This field is required.';
  if (input.validity.typeMismatch)  return 'Please enter a valid ' + input.type + '.';
  if (input.validity.tooShort)      return 'Minimum ' + input.minLength + ' characters.';
  if (input.validity.patternMismatch) return input.title || 'Invalid format.';
  return 'Invalid value.';
}

Fieldsets and Select Elements

<fieldset>
  <legend>Preferred contact method</legend>
  <label><input type="radio" name="contact" value="email"> Email</label>
  <label><input type="radio" name="contact" value="phone"> Phone</label>
</fieldset>

<select name="country" required>
  <option value="">Select a country…</option>
  <optgroup label="North America">
    <option value="us">United States</option>
    <option value="ca">Canada</option>
  </optgroup>
</select>

Best Practices

  • Always pair <label for="id"> with every input — required for screen readers
  • Use autocomplete attributes to enable browser autofill
  • Place error messages adjacent to the field, not only in a summary at the top
  • Never rely on client-side validation alone — validate on the server too
  • Use aria-describedby to link inputs to their error messages

Continue Learning