# Form labels: every input needs one (no redesign required)

> A field without a label is announced as "edit text" and nothing else. Learn the four ways to name a field, when a hidden label is fine, and the form plugin settings.

Updated 2026-09-25 · Accessibility · HTML version: https://getreport.app/guides/form-labels-every-input-needs-one

A newsletter box with a grey "Your email" placeholder looks tidy. To a screen reader user it is announced as "edit text", with nothing to say what goes in it, and once someone starts typing the placeholder is gone for everyone. Missing labels are one of the findings accessibility checks rate critical, and they are among the quickest to fix: one element or one attribute per field, usually with no visible change. This guide explains what a label actually does, the four ways to add one, and where the setting hides in the common form plugins.

## Quick answer

- Every `<input>`, `<select>` and `<textarea>` needs an *accessible name*, the words a screen reader announces for it.
- Best: a visible `<label for="id">` that matches the field's `id`, or a `<label>` wrapped around the field.
- No room in the design (search box, one-field newsletter)? Keep the `<label>` and hide it visually with a `.visually-hidden` class, or use `aria-label`.
- A placeholder is not a label. A `title` attribute is a weak one.
- Groups of radio buttons and checkboxes need a `<fieldset>` with a `<legend>` for the question.
- Run the [accessibility checker](https://getreport.app/tools/accessibility-checker) and re-run it after each change.

## Why form labels matter

A label does three jobs. It tells screen reader users what a field is for: "Email address, edit text, required" instead of "edit text". It gives voice control users something to say ("click email address"). And it makes the field easier to hit: clicking or tapping a connected label focuses the field, which turns a 20-pixel checkbox into a target the size of its text.

Without labels, forms fail at the moment that matters. A screen reader user on a checkout with six unlabelled fields has to guess which one is the postcode. A visitor with a memory impairment who starts typing into a placeholder-only field loses the instruction the moment the first letter appears. Browser autofill also leans on labels and `autocomplete` values to decide what to fill where, so unlabelled forms get filled wrong for everyone.

## What an accessible name is

Every interactive element has an accessible name that the browser computes and passes to assistive technology. For a form field, the browser looks at these sources, in this order, and uses the first one it finds:

1. `aria-labelledby`: the text of other elements, referenced by id.
2. `aria-label`: a text string on the field itself.
3. A `<label>`: connected with `for`/`id`, or wrapped around the field.
4. The `title` attribute.
5. The `placeholder` attribute, as a last resort.

The first three are real labels (the [form labels learn page](https://getreport.app/learn/form-labels) has the one-screen summary). `title` shows only as a mouse tooltip, never on touch screens or to keyboard users, so it helps screen readers and nobody else. A placeholder is read by some screen readers and not others, disappears as soon as someone types, and is usually too pale to meet the contrast ratio.

## How getReport checks it

> **Free tool:** [Accessibility checker (WCAG 2.2)](https://getreport.app/tools/accessibility-checker): axe-core violations by impact, colour contrast, landmarks, form labels, link names, tap targets and zoom — measured on the rendered page, with the selectors to fix.

The report renders the page in Chromium and runs axe-core. The form labels finding gathers the rules about field names: fields with no name at all (`label`), selects without a name (`select-name`), fields whose only name is a `title` or a description (`label-title-only`), and custom ARIA inputs and toggles without a name. It appears only on pages that have form fields. For the other critical findings that tend to sit next to this one, see [fixing the top accessibility checker findings](https://getreport.app/guides/accessibility-checker-top-findings).

> **Check: Every form field has a label.** Without a label a screen reader announces only "edit text", so visitors cannot tell the email field from the search box. Labels also make the tap target bigger on phones.
>
> 1. Give every input, select and textarea a <label for="…"> that matches its id, or wrap the field in the label.
> 2. Where a visible label does not fit the design, add aria-label="…" to the field; a placeholder is not a label.

![The form labels finding opened: the title counts the fields without a label, and the technical detail lists the axe rule with the number of affected elements and the CSS selector of each field](https://getreport.app/guides/img/form-labels-every-input-needs-one/finding.webp "Each selector points at one field without an accessible name; a newsletter or search form in the footer usually appears on every page.")

What the card does not say:

- **Axe accepts a placeholder as a name.** A field whose only label is `placeholder="Email"` passes axe's rule because the browser falls back to it. It still fails people for the reasons above, so check for placeholder-only fields by eye: any field with grey text inside and no words above or beside it.
- **Missing labels also count as critical violations.** Axe rates an unnamed input or select as critical, so the same fields show up in the critical count. Fixing the labels clears both.
- **The submit button is a separate check.** An icon-only search button (a magnifying glass) with no text is a button without a name, reported under link and button names.

> **Check: No critical accessibility violations.** Critical issues stop some visitors from using the page at all, for example buttons a screen reader cannot name or forms that cannot be filled in.
>
> 1. Open the technical detail to see each rule and the elements affected.
> 2. Fix the highest-count rule first; the same template usually causes most instances.

> **Check: Every link and button has an accessible name.** Icon-only links and buttons are read out as "link" or "button" with nothing else, so screen reader users cannot tell the cart from the search. Search engines also use link text to understand the target page.
>
> 1. Add visible text, or aria-label="…" on the link or button, that says where it goes or what it does.
> 2. For icon buttons, put the text inside the button and hide it visually with a screen-reader-only class.

## Step by step

### 1. Find the fields on the page

Open the finding and look at the selectors, for example `#mc-embedded-subscribe-form input[type="email"]` or `.search-form > input`. Most sites have three kinds of form: a search box in the header, a newsletter field in the footer, and one real form (contact, checkout, booking). The header and footer ones repeat on every page, so they are worth fixing first.

### 2. Add a visible label where there is room

The strongest pattern is a visible label connected by `for` and `id`. The `id` must be unique on the page.

```html
<label for="contact-email">Email address</label>
<input type="email" id="contact-email" name="email" autocomplete="email" required>
```

Wrapping the field in the label works too, and needs no ids, which helps when the same form appears twice on a page:

```html
<label>
  Email address
  <input type="email" name="email" autocomplete="email" required>
</label>
```

### 3. Where there is no room, hide the label visually, not from everyone

A search box with a magnifying-glass button, or a one-field newsletter sign-up under a heading that already says "Get our newsletter", does not need visible label text. It still needs a label. Keep the `<label>` in the HTML and hide it with a class that removes it from the screen but not from the accessibility tree:

```css
/* global stylesheet */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
```

```html
<form role="search" action="/search" method="get">
  <label for="site-search" class="visually-hidden">Search the shop</label>
  <input type="search" id="site-search" name="q" placeholder="Search products">
  <button type="submit">
    <svg aria-hidden="true" focusable="false" width="20" height="20"><use href="#icon-search"></use></svg>
    <span class="visually-hidden">Search</span>
  </button>
</form>
```

Do not hide labels with `display: none` or `visibility: hidden`: those remove the text for screen readers too, and when the field has no other name, axe reports the hidden label. WordPress themes already ship this class as `.screen-reader-text`; Shopify's Dawn-based themes as `.visually-hidden`. Use the one your theme has.

### 4. Or use aria-label or aria-labelledby

When you cannot add an element (a field generated by a script you can only configure, say), `aria-label` names the field directly:

```html
<input type="email" name="email" aria-label="Email address for the newsletter" placeholder="you@example.com">
```

When the label text is already on the page, `aria-labelledby` points at it by id, which keeps visible and spoken text identical. A quantity field in a cart row can be named by the product name and the column heading together:

```html
<th id="col-qty">Quantity</th>
…
<td>
  <span id="item-42">Alpina Pro trekking shoe, size 42</span>
  <input type="number" name="qty[42]" value="1" min="0" aria-labelledby="col-qty item-42">
</td>
```

The field is announced as "Quantity Alpina Pro trekking shoe, size 42". Prefer a visible `<label>` when you can: `aria-label` is invisible to sighted users, and voice control users cannot say words they cannot see.

### 5. Group radio buttons and checkboxes with fieldset and legend

Each radio button gets its own label, and the question they answer goes in a `<legend>`. Screen readers announce the legend when focus enters the group, so "Standard" becomes something like "Delivery method, Standard, 3–5 working days, €4.90, radio button, 1 of 2".

```html
<fieldset>
  <legend>Delivery method</legend>
  <input type="radio" id="ship-standard" name="shipping" value="standard" checked>
  <label for="ship-standard">Standard, 3–5 working days, €4.90</label>
  <input type="radio" id="ship-express" name="shipping" value="express">
  <label for="ship-express">Express, next working day, €9.90</label>
</fieldset>
```

A single checkbox ("I agree to the terms") does not need a fieldset, just its label.

### 6. Connect hints and error messages with aria-describedby

The label says what the field is; the description says how to fill it or what went wrong. Connect both with `aria-describedby`, which accepts several ids separated by spaces, and mark a field with an error as `aria-invalid="true"`:

```html
<label for="password">Password</label>
<input type="password" id="password" name="password" autocomplete="new-password"
       aria-describedby="password-hint password-error" aria-invalid="true">
<p id="password-hint">At least 12 characters.</p>
<p id="password-error" class="field-error">This password has 8 characters. Add 4 more.</p>
```

Error text should say what to do, not only what is wrong, and should not rely on a red border alone: colour-only errors fail people who cannot tell red from grey.

### 7. Add autocomplete to personal-data fields

WCAG 2.2 AA (criterion 1.3.5) asks for `autocomplete` on fields that collect information about the user. It lets browsers and password managers fill the form, which saves everyone typing and helps people with motor or memory impairments most. The common values:

```text
name  given-name  family-name  email  tel  organization
street-address  address-line1  address-line2  postal-code  address-level2 (city)  country-name
username  current-password  new-password  one-time-code
cc-name  cc-number  cc-exp  cc-csc
```

Axe checks that the values you use are valid (`autocomplete="e-mail"` is not); an invalid one appears under serious violations.

### 8. Re-run the checker

Run the page again. The finding should read "Every form field has a label". Then try the form yourself with the keyboard only: Tab into each field, and check that the label is visible or announced and that the error messages appear next to the field.

## Platform notes

### WordPress

- **Contact Form 7**: labels are part of the form template you edit under Contact → Contact Forms. The default template wraps each field tag in a `<label>`; forms built from scratch often drop it. The pattern, with CF7's `autocomplete` option:

```html
<label> Your email
    [email* your-email autocomplete:email] </label>
```

For checkboxes and radio buttons, add the `use_label_element` option to the tag so each choice gets its own label: `[checkbox your-topics use_label_element "Orders" "Returns"]`.

- **Gravity Forms and WPForms**: every field has a label setting, and both let you hide it in the field's appearance or advanced options. Keep labels visible where the layout allows; if you hide one, re-run the checker to confirm the field still has a name.
- **The theme's search form**: WordPress core's search form already contains a visually hidden "Search for:" label. Themes that ship their own `searchform.php` sometimes remove it. In block themes, the Search block has a label toggle; with the label hidden, core still outputs it with the `screen-reader-text` class.
- **Newsletter embeds** (Mailchimp, MailerLite, Brevo) are often trimmed down to a single field to fit a footer, and the label goes with the trimming. In the embed code, add a `<label>` with the input's `id`, or `aria-label` on the input if the code is regenerated by the plugin.

### Shopify

Search, newsletter and contact forms live in the theme: Online Store → Themes → … → Edit code, in the `sections` and `snippets` folders (the footer section holds the newsletter form built with `{% form 'customer' %}`). Current themes from the Theme Store label their fields; the unlabelled ones usually come from pop-up and newsletter apps, which have their own settings for field labels, or from custom sections copied in by hand.

### Static sites and custom builds

If you use a component library, fix the input component once so it always renders a label (a required `label` prop is a good guard). Search the templates for `placeholder=` without a matching `<label` nearby; that finds most cases quickly.

## Verify

- The form labels finding reads "Every form field has a label", and the critical violations count dropped by the same number of fields.
- Clicking a label text focuses its field (for `for`/`id` and wrapping labels).
- With a screen reader, each field is announced by its label; the 20-minute test in [testing your site with a screen reader](https://getreport.app/guides/testing-with-a-screen-reader) includes a form pass.
- Browser autofill puts the right values in name, email and address fields.

## Common mistakes

- **Placeholder as the only label.** Axe lets it pass, people do not. Add a label above the field and keep the placeholder for an example value, if at all.
- **A `for` that points nowhere.** A label with `for="email"` next to an input with `id="email-1"` names nothing. The two must match exactly, and each `id` must be unique; a form that appears twice on one page needs different ids.
- **Hiding the label with `display: none`.** It disappears for screen readers too. Use the visually hidden class.
- **`aria-label` that differs from the visible text.** A field labelled "Email" on screen and `aria-label="Subscribe"` in code confuses voice control users. Keep them the same, or start the `aria-label` with the visible words.
- **One label for a group of fields.** A "Date of birth" label above three selects (day, month, year) names only one of them. Use a fieldset with the legend "Date of birth" and a label for each select.
