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"withinputmode="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:
- A clear message that explains what went wrong
- Where the error is (which field)
- How to fix it
- Errors announced to assistive technology
- Focus moved to the right place after submit
Mark the field as invalid
Use:
aria-invalid="true"on the input when there is an erroraria-describedbyto 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
divelements as “fake inputs”.
9) Helpful attributes: autocomplete, inputmode, and constraints
Use attributes to reduce friction:
autocompletehelps users fill quickly (especially on mobile).required,minlength,maxlength,patterncan support client-side checks (but still validate on the server).inputmodeimproves 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-pressedaccordingly
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-invalidandaria-describedbyare applied correctly - [ ] An error summary is provided for longer forms and receives focus
- [ ] Full keyboard navigation works, with visible focus
- [ ] Appropriate
type,autocomplete, andinputmodeare 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