Lesson Progress
0% Complete

Forms are where users sign up, log in, pay, search, and contact you. If forms are difficult to use on mobile, or confusing for screen reader and keyboard users, people will abandon them. This topic focuses on building forms that are responsive, clear, and WCAG-aligned.


1) Form structure that works for everyone

Use semantic HTML first

Rely on native form elements where possible. They come with built-in accessibility and mobile-friendly behaviour.

  • Use a real <form> element for submissions.
  • Pair every input with a <label> (not just placeholder text).
  • Group related fields with <fieldset> and <legend> (especially for radio buttons and checkboxes).

Example: basic structure

<form action="/signup" method="post" novalidate>
  <h1>Create an account</h1>

  <div class="field">
    <label for="email">Email address</label>
    <input id="email" name="email" type="email" autocomplete="email" required>
  </div>

  <div class="field">
    <label for="password">Password</label>
    <input id="password" name="password" type="password" autocomplete="new-password" required>
  </div>

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

Why this matters

  • Screen readers announce labels and input types correctly.
  • Mobile devices display appropriate keyboards (e.g. email keyboard for type="email").
  • Browsers can assist with autofill and validation.

2) Responsive layout patterns for forms

Forms should be comfortable to complete on small screens, and efficient on larger screens.

Recommended responsive approach

  • Mobile-first: single-column layout by default.
  • Increase spacing and touch targets for thumbs.
  • Use CSS Grid/Flex to shift into multi-column layouts only when there is enough width.

Example: responsive form layout

form {
  max-width: 42rem;
  margin: 0 auto;
  padding: 1rem;
}

.field {
  margin-bottom: 1rem;
}

label {
  display: block;
  margin-bottom: 0.4rem;
  font-weight: 600;
}

input, select, textarea {
  width: 100%;
  padding: 0.75rem;
  font-size: 1rem;
}

@media (min-width: 768px) {
  .two-col {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
  }
}

Example: using the two-column area

<div class="two-col">
  <div class="field">
    <label for="firstName">First name</label>
    <input id="firstName" name="firstName" autocomplete="given-name" required>
  </div>

  <div class="field">
    <label for="lastName">Last name</label>
    <input id="lastName" name="lastName" autocomplete="family-name" required>
  </div>
</div>

Touch-friendly inputs (mobile usability)

  • Aim for comfortable height (around 44px equivalent).
  • Keep enough space between controls to avoid mis-taps.
  • Avoid placing multiple small checkboxes too close together.

3) Labels, placeholders, hints, and help text

Labels are required

A placeholder is not a label. Placeholders disappear as users type and may have low contrast.

Good pattern

  • Use a visible <label>
  • Add hint text beneath when needed (format rules, examples, constraints)

Example: hint text linked to input

<div class="field">
  <label for="saId">SA ID number</label>
  <input id="saId" name="saId" inputmode="numeric" aria-describedby="saIdHint">
  <p id="saIdHint" class="hint">13 digits, numbers only.</p>
</div>

Tip: inputmode="numeric" suggests a numeric keypad without forcing type="number" (which can be awkward for IDs, phone numbers, and leading zeros).


4) Inclusive input patterns (choose the right control)

Picking the correct input type improves accessibility, validation, and mobile keyboards:

  • Email: type="email", autocomplete="email"
  • Phone: type="tel", autocomplete="tel"
  • Date: consider type="date" (test support), or use accessible date inputs
  • Password: type="password", allow show/hide toggle (accessible)
  • Numbers that are not quantities (IDs, account numbers): use type="text" with inputmode="numeric"

Radio buttons vs checkboxes

  • Radio buttons: choose one option from a set.
  • Checkboxes: select multiple options, or a single on/off choice.

Group them using <fieldset> and <legend> so screen readers understand the relationship.

<fieldset>
  <legend>Preferred contact method</legend>

  <div>
    <input type="radio" id="contactEmail" name="contactMethod" value="email" required>
    <label for="contactEmail">Email</label>
  </div>

  <div>
    <input type="radio" id="contactSms" name="contactMethod" value="sms">
    <label for="contactSms">SMS</label>
  </div>
</fieldset>

5) Validation: clear, polite, and accessible

Validation should help users finish successfully, not punish them.

Use both client-side and server-side validation

  • Client-side (HTML/JS) gives immediate feedback.
  • Server-side is essential for security and correctness (client-side can be bypassed).

When to validate (UX guidance)

  • Validate obvious things as the user types (e.g. password strength), but don’t spam errors while they are still entering data.
  • Validate on blur (when leaving the field) for formatting rules.
  • Always validate again on submit and show a clear summary.

6) Accessible error messaging and error handling

A good error experience includes:

  1. A clear message that explains what went wrong
  2. Where the error is (which field)
  3. How to fix it
  4. Errors announced to assistive technology
  5. Focus moved to the right place after submit

Mark the field as invalid

Use:

  • aria-invalid="true" on the input when there is an error
  • aria-describedby to connect the input to the error message

Example: field-level error

<div class="field">
  <label for="email">Email address</label>
  <input
    id="email"
    name="email"
    type="email"
    autocomplete="email"
    aria-invalid="true"
    aria-describedby="emailError"
    required
  >
  <p id="emailError" class="error">Enter a valid email address, like name@example.com.</p>
</div>

Provide an error summary (especially on longer forms)

An error summary helps users quickly see what needs attention. It’s also useful for screen reader users when focus is moved to the top after submit.

Example: error summary with links

<div class="error-summary" role="alert" tabindex="-1" id="errorSummary">
  <h2>There is a problem</h2>
  <ul>
    <li><a href="#email">Enter a valid email address.</a></li>
    <li><a href="#password">Password must be at least 8 characters.</a></li>
  </ul>
</div>

Key points

  • role="alert" ensures it is announced.
  • tabindex="-1" allows you to move focus to it with JavaScript.
  • Links jump directly to problematic fields.

Typical submit behaviour

  • If errors exist: prevent submission, show summary, move focus to summary.
  • If no errors: submit, show success state/page, or inline confirmation.

7) Visual design for errors (not colour-only)

Do not rely on colour alone to indicate an error (WCAG). Use at least two signals:

  • Text message (what and how to fix)
  • Icon or border style change
  • Strong focus outline for keyboard users

Example styles

.error {
  color: #b00020;
  margin-top: 0.4rem;
}

input[aria-invalid="true"] {
  border: 2px solid #b00020;
}

.error-summary {
  border: 2px solid #b00020;
  padding: 1rem;
  margin-bottom: 1rem;
  background: #fff5f7;
}

8) Keyboard and focus support (must-have)

Many users navigate forms using a keyboard, switch device, or screen reader.

Checklist:

  • Tab order matches the visual order.
  • You can reach every control using Tab/Shift+Tab.
  • Buttons and links are accessible with Enter/Space where appropriate.
  • The focus indicator is visible (don’t remove outlines without a strong replacement).
  • After validation errors, focus goes to the summary or first invalid field.

Avoid

  • Custom dropdowns and widgets without full keyboard support.
  • Using div elements as “fake inputs”.

9) Helpful attributes: autocomplete, inputmode, and constraints

Use attributes to reduce friction:

  • autocomplete helps users fill quickly (especially on mobile).
  • required, minlength, maxlength, pattern can support client-side checks (but still validate on the server).
  • inputmode improves mobile keyboards.

Examples

<input name="city" autocomplete="address-level2">
<input name="postalCode" autocomplete="postal-code" inputmode="numeric">
<input name="name" autocomplete="name" required>

10) Responsive form controls: select menus, textareas, and buttons

Textareas

  • Allow enough height for readability.
  • Don’t trap users with tiny scrolling areas.
textarea {
  min-height: 8rem;
  resize: vertical;
}

Buttons

  • Ensure good size and spacing.
  • Use clear action wording: “Save changes”, “Send message”, “Pay now”.
  • Avoid vague labels like “Submit”.

11) Accessible “Show password” toggle (common requirement)

If you offer a show/hide feature, keep it keyboard accessible and announce state.

Example

<div class="field">
  <label for="password">Password</label>
  <div class="password-wrap">
    <input id="password" name="password" type="password" autocomplete="current-password" required>
    <button type="button" id="togglePassword" aria-controls="password" aria-pressed="false">
      Show
    </button>
  </div>
</div>

Behaviour

  • Toggling updates type="text" / type="password"
  • Update button text and aria-pressed accordingly

12) Practical checklist (responsive + accessible forms)

Use this as a final QA list:

  • [ ] Every input has a correctly associated <label for="...">
  • [ ] Groups use <fieldset> and <legend> where applicable
  • [ ] Layout works from small screens upward (no horizontal scrolling)
  • [ ] Touch targets are large enough and spaced out
  • [ ] Validation messages are specific and helpful
  • [ ] Errors are not colour-only (text + visual indicator)
  • [ ] aria-invalid and aria-describedby are applied correctly
  • [ ] An error summary is provided for longer forms and receives focus
  • [ ] Full keyboard navigation works, with visible focus
  • [ ] Appropriate type, autocomplete, and inputmode are used
  • [ ] Server-side validation exists and matches client-side rules

Quick activity (hands-on)

Create a responsive “Contact us” form with:

  • Name, email, phone, message (textarea)
  • One required checkbox: “I agree to the privacy policy”
  • Client-side validation on submit
  • Field-level errors + an error summary at the top
  • A mobile-first layout that becomes two columns for name/email on tablets and above

Test it using:

  • Keyboard only (Tab/Shift+Tab, Enter, Space)
  • A small screen viewport (responsive mode)
  • A screen reader (if available) or at least by checking focus and error announcements via role="alert" and correct label associations