Skip to content
Barrierefrei²

Accessible Forms: Labels, Error Messages and Logins Without Hurdles

Forms decide whether people check out, sign up or give up. Here’s how to build fields with clear labels, helpful error messages and a login that doesn’t lock anyone out.

Forms are where things get real: completing a purchase, booking an appointment, creating an account, submitting an application. They’re also where many people get stuck. A field without a label, an error shown only as a red border, an image puzzle at login, a session that silently expires after a few minutes – and the task is over.

Accessible forms help far more people than screen reader users. People with attention or memory difficulties benefit from clear instructions, people with motor impairments from fields that fill themselves in, and anyone typing on a phone on the go from error messages that actually make sense. For online shops in the EU, it’s also a legal matter: under the European Accessibility Act, which Germany implements through its Accessibility Strengthening Act (BFSG), e-commerce services must meet WCAG 2.1 level AA via the EN 301 549 standard, and forms sit at the heart of every checkout.

The good news is that most of the solutions are plain, semantic HTML. This guide walks you through labels, required fields, hints, grouping, autocomplete, error messages, time limits and authentication, each with a short code example.

Visible labels, not placeholders

Every input needs a visible label that stays put. That’s what 3.3.2 Labels or Instructions asks for. A placeholder is not a label: it disappears as soon as you start typing, usually has poor contrast and isn’t reliably announced as the field’s name by every assistive technology. By the time you’re correcting an entry, you’ve forgotten what the field was for.

The programmatic connection matters just as much. A label element with a matching for attribute makes screen readers announce the field’s name, and clicking the label focuses the field. For small checkboxes, that noticeably enlarges the click target.

<!-- Before: placeholder only, no label -->
<input type="email" placeholder="Email address">

<!-- After: visible, associated label -->
<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email">

Put the label above or to the left of the field, never inside it. Also make sure the visible text is part of the field’s accessible name. Someone using voice control who says “click email address” expects exactly that field to respond.

Layout plays a part too. A single-column form that reads top to bottom is easier for most people to follow than fields scattered side by side. It also keeps the label in view for people using high zoom. Small exceptions, like city and postcode on one line, are fine as long as the order stays logical.

Mark required fields clearly

People need to know which fields are required before they start. Two approaches work well: either write “(required)” right in the label, or mark required fields with an asterisk and explain it at the top of the form. If nearly every field is required, it’s often clearer to flag the few optional ones with “(optional)” instead.

In your markup, add the required attribute so screen readers announce the field as required regardless of the visual marker. Don’t rely on color alone, though: a red label that’s the only indicator doesn’t meet the color and contrast requirements.

<p>Fields marked * are required.</p>

<label for="first-name">First name *</label>
<input id="first-name" name="first-name" autocomplete="given-name" required>

Hints and format requirements

If you expect a specific format, say so up front, not in the error message. A hint like “8 digits, printed on your invoice” or “At least 12 characters” sits visibly below the label and is connected to the field with aria-describedby. Screen readers then read it after the field’s name and type.

Even better, don’t enforce formats at all. Accept phone numbers with or without spaces, IBANs with or without grouping, and dates in common notations. Normalizing input is your code’s job, not your users’.

Pick the right input type as well. type="email", type="tel" or inputmode="numeric" bring up the right keyboard on mobile devices. type="number", on the other hand, is a poor fit for postcodes, customer numbers or card numbers: leading zeros can get lost, and the scroll wheel can change the value by accident.

Group related fields

For radio buttons and checkbox groups, each option’s label isn’t enough on its own. A screen reader user landing on “Express (1 business day)” also needs to know the question is about shipping. That’s what fieldset and legend are for: the legend is announced when you enter the group.

<fieldset>
  <legend>Shipping method</legend>
  <input type="radio" id="standard" name="shipping" value="standard">
  <label for="standard">Standard (3–5 business days)</label>
  <input type="radio" id="express" name="shipping" value="express">
  <label for="express">Express (1 business day)</label>
</fieldset>

Longer forms also benefit from groups like “Billing address” and “Shipping address”. Keep legends short, since some screen readers repeat them for every field in the group.

autocomplete: let the browser fill it in

Success criterion 1.3.5 Identify Input Purpose requires fields that collect personal data to declare their purpose in a machine-readable way. In HTML, that’s the autocomplete attribute. Browsers and password managers can then fill fields reliably, sparing people with motor or cognitive disabilities a lot of typing and a lot of mistakes.

<label for="street">Street address</label>
<input id="street" name="street" autocomplete="address-line1">

<label for="city">City</label>
<input id="city" name="city" autocomplete="address-level2">

<label for="postcode">Postcode</label>
<input id="postcode" name="postcode" autocomplete="postal-code">

Common values include given-name, family-name, email, tel, address-line1, postal-code, address-level2 for the city, country-name and bday. The full list is in the “Input Purposes” section of WCAG. And don’t set autocomplete="off" across the board just because your form builder does it by default.

Error messages that actually help

Mistakes happen. What matters is how quickly people can recover. Under 3.3.1 Error Identification, a detected error must be described in text and the affected field identified. 3.3.3 Error Suggestion goes further: if you know how to fix the problem, say so.

The message next to the field

Compare “Invalid input” with “The customer number has only 7 digits. Enter all 8 digits from your invoice.” The second tells people what went wrong and what to do. It sits right next to the field, is connected via aria-describedby, and aria-invalid="true" flags the field as invalid. A warning icon or the word “Error” makes sure the message doesn’t depend on color.

<label for="customer-number">Customer number</label>
<p id="customer-number-hint">8 digits, printed on your invoice</p>
<input id="customer-number" name="customer-number" inputmode="numeric"
       aria-invalid="true"
       aria-describedby="customer-number-hint customer-number-error">
<p id="customer-number-error">Error: The customer number has only 7 digits.
  Enter all 8 digits from your invoice.</p>

Once the error is fixed, remove aria-invalid and the message. And make sure everything people entered survives the round trip: nobody should have to retype a whole form because of one typo.

The error summary

For longer forms, add a summary above the form that lists every error and links to the affected field. After a failed submission, move focus to the summary with JavaScript so screen reader users immediately learn what happened.

<div id="error-summary" tabindex="-1">
  <h2>There are 2 problems with your details</h2>
  <ul>
    <li><a href="#customer-number">Customer number: enter all 8 digits.</a></li>
    <li><a href="#email">Email address: the @ symbol is missing.</a></li>
  </ul>
</div>

<script>
  document.getElementById("error-summary").focus();
</script>

If submitting the form reloads the page, update the page title too, for example to “Error: Contact form”. Screen readers read the title first when a page loads, and the browser tab shows at a glance that something went wrong.

Timing matters

Don’t validate on every keystroke. An error that pops up after the first letter of an email address is confusing and triggers needless screen reader announcements. Validating when someone leaves a field and when they submit works well. And don’t disable the submit button until the form is complete: a grayed-out button doesn’t explain what’s missing, but a click followed by an error summary does.

Success messages count too. If a message like “Your message has been sent” appears without a page change, put it in an element with role="status". It’s announced without moving focus, as WCAG’s criterion 4.1.3 on status messages expects. One catch: the container must already exist when the page loads; only the text gets inserted later.

Less typing, safer submissions

New in WCAG 2.2, 3.3.7 Redundant Entry says information someone has already entered in the same process should be filled in automatically or offered for selection, not requested again. The classic example is a “Shipping address is the same as billing address” checkbox. Exceptions apply where re-entry is essential, for instance confirming a new password for security.

For transactions with legal or financial consequences, such as orders, contracts or payments, 3.3.4 Error Prevention (Legal, Financial, Data) requires a safety net: submissions are reversible, checked for errors, or shown for review before they’re final. A “Review your order” page with edit links does the job and tends to cut returns and support requests along the way.

Fair time limits

Sessions that expire quickly hit people who read or type more slowly, or who use text-to-speech, especially hard. That’s why 2.2.1 Timing Adjustable requires that time limits can be turned off, adjusted or extended. In practice, that usually means a warning before the session ends and a simple action like “Stay signed in”. People must get at least 20 seconds to respond, and they must be able to extend at least ten times.

Exceptions cover real-time events like auctions, cases where the time limit is essential, and limits longer than 20 hours. Wherever you can, save progress so that an expired session doesn’t wipe out everything.

Logins without puzzles

Also new in WCAG 2.2 is 3.3.8 Accessible Authentication (Minimum). Logging in must not depend on a cognitive function test – remembering something, transcribing characters or solving a puzzle – unless there’s an alternative or some help. Passwords are fine as long as password managers can fill them in and pasting works.

<label for="password">Password</label>
<input id="password" name="password" type="password"
       autocomplete="current-password">

<label for="otp">Verification code from your text message</label>
<input id="otp" name="otp" inputmode="numeric"
       autocomplete="one-time-code">

In practice, that means:

  • Never block pasting into password or code fields.
  • Use current-password and new-password so password managers work reliably.
  • Don’t split one-time codes into six separate boxes that won’t accept a paste.
  • Offer alternatives like passkeys or a sign-in link by email.
  • Skip text CAPTCHAs with distorted characters. Image puzzles like “select all traffic lights” are allowed at level AA, but they lock out blind users unless there’s an alternative.

Think twice before building custom controls

Native elements like select, input type="checkbox" or input type="date" aren’t always pretty, but they come with keyboard support, focus handling, role and state built in. A custom dropdown made of div elements has to recreate all of it: arrow keys, Escape, announcing the selected value, sensible behavior on mobile. Teams rarely get every detail right.

Before you build your own, check whether the native control can be styled enough with CSS. Checkboxes and radio buttons are easy to customize today with accent-color or appearance: none while keeping their semantics. If you really need a custom component, such as a search field with a suggestion list, follow the patterns in the WAI-ARIA Authoring Practices and test it with a keyboard and a screen reader.

Checklist

  • Every field has a visible label connected with for and id.
  • Required fields are marked in text and use required.
  • Format requirements appear before input and are connected with aria-describedby.
  • Radio buttons and checkbox groups sit inside a fieldset with a legend.
  • Personal data fields have the right autocomplete values.
  • Error messages name the field, the problem and the fix, and don’t rely on color.
  • After a failed submission, focus moves to a linked error summary.
  • Information already entered isn’t requested again.
  • Time limits can be extended.
  • Login works with password managers and pasting, and without puzzles.

Conclusion

Accessible forms aren’t a special build. They’re mostly made of HTML elements that have been around for decades – label, fieldset, legend, required and autocomplete – plus well-written hints and error messages that treat people with respect instead of scolding them.

Before you ship, go through your form once without a mouse, as described in our guide to keyboard and focus, and once with a screen reader. Our quick screen reader test shows how to do that in 15 minutes. Every issue you catch is one less abandoned form.

This article is general information, not legal advice. You’ll find every criterion mentioned here explained in detail in the WCAG guide.

Sources

Back to the blog