How to fix missing form labels, conflicting labels, and missing required attributes?

form-accessibility-issues page

The issue

A form field without a label can’t be filled in by someone using a screen reader, because it is announced only by its type: “edit text”, “checkbox”, “combobox”, with nothing to say what it is for. A login form with two unlabeled fields is two guesses, and the person may give up on the form entirely. Forms collect sign-ups, payments and searches, so the loss is the transaction.

The cause is a control with no programmatic label: no <label> whose for matches its id, no <label> around it, and no aria-label or aria-labelledby. Two related problems make it worse. A field with several <label> elements is handled inconsistently by assistive technology, so what a screen reader announces may not be what a sighted person reads. A field marked required only by an asterisk or a color gives no warning to anyone who can’t see it, and they learn about it from a failed submission. WCAG 1.3.1 requires the relationship between a label and its control to be programmatically determinable.

Scope

Each <input>, <select> and <textarea> on the page is checked on its own, along with custom controls built with ARIA roles. Forms rendered by scripts count once they are in the page. A form component used across a site carries the problem to every page it appears on, and fixing the component fixes them all.

How to check you have it

  • In probeo: the page reports the label rule, “Form elements must have labels”, or form-field-multiple-labels, “Form field should not have multiple label elements”. Related rules cover select-name, input-button-name and label-title-only, which flags a field whose only label is its title.
  • By hand: for each field, find the <label> that points at it, or the aria-label or aria-labelledby on it. Check that there is exactly one, and that each required field has required or aria-required="true".
  • With a tool: the accessibility pane in Chrome or Firefox DevTools shows each field’s computed name and whether it is marked required.

Probeo checks labels. It doesn’t check whether required fields are marked as required, so check those by hand.

The fix

Give every form control exactly one programmatic label, and mark required fields with required or aria-required.

The most widely supported label is a <label> with a for that matches the field’s id, and clicking it also focuses the field. Wrapping the field in its <label> works too, without the for. Where a visible label doesn’t suit the design, such as a search field next to a “Search” button, use aria-label, or aria-labelledby pointing to visible text. Remove any second label: when a field has both a <label> and aria-labelledby, the aria-labelledby wins and the visible label is ignored.

Add required to every field that must be filled in. It gives native validation and an announcement. If the form turns native validation off with novalidate, use aria-required="true" so screen readers still announce it.

Done when: every field has one label and required fields say so, and the check above comes back clean.

Do not

  • Do not use placeholder text as a field’s only label; it disappears when the person starts typing.
  • Do not give a field both a <label> and an aria-labelledby unless the visible label’s text is part of what aria-labelledby points to.
  • Do not mark a field as required with an asterisk or a color alone.

FAQ

Can placeholder text serve as a form label?

No. Placeholder text disappears the moment a user begins typing, removing the only identification of what the field expects. Users who look away and return to a partially filled form cannot verify whether they entered data in the correct field. Screen readers may announce placeholder text as a fallback, but support is inconsistent and the behavior differs from a proper label announcement. Placeholder text is supplementary — it can show format hints (MM/DD/YYYY) or examples, but the field’s identity must come from a label element, aria-label, or aria-labelledby that persists regardless of input state.

When should visually hidden labels be used instead of visible labels?

Visually hidden labels apply when the field’s purpose is unambiguous from visual context but requires a programmatic name for assistive technology. A search input adjacent to a button labeled “Search” is a common case — sighted users understand the field’s purpose from the button, but screen reader users need the input to announce “Search” independently. Use CSS to hide the label visually (position: absolute, clip, or the sr-only utility class) while keeping it in the DOM for assistive technology. Don’t use display: none or visibility: hidden: the accessible name computation skips hidden content, so the label may not count at all.

Fieldset groups related controls under a shared label provided by the legend element. Radio buttons and checkbox groups are the primary use case — without a fieldset, each radio button announces its own label but not the question it answers. A group of radio buttons labeled “Yes” and “No” is meaningless without the fieldset legend “Do you agree to the terms?” which provides the group context. Screen readers announce the legend when a user enters the fieldset, then announce individual labels within it. Fieldset/legend is not required for every group of fields, but any group where individual labels are ambiguous without shared context needs it.

What happens when a single input has both a label element and aria-labelledby?

The accessible name computation algorithm specifies a precedence order. aria-labelledby takes highest precedence for the accessible name. If an input has a label element with text “Email address” and an aria-labelledby pointing to a span containing “Your email,” screen readers announce “Your email” and ignore the label element’s text entirely. Sighted users still see “Email address” as the visible label. This mismatch violates WCAG 2.5.3 (Label in Name), which requires the visible label text to be contained within the accessible name. The fix is to use one mechanism, not both.

How does the required attribute differ from aria-required?

The HTML5 required attribute provides both native browser validation (the form will not submit if the field is empty) and an accessibility announcement. aria-required=“true” provides only the accessibility announcement — it tells screen readers the field is required but does not trigger native validation. Use required when native validation is appropriate. Use aria-required when the form handles validation in JavaScript (with novalidate on the form element) and native validation is suppressed. Both attributes cause screen readers to announce “required” when the field receives focus. They can coexist without conflict, but having both is redundant if native validation is active.

How should custom form controls (dropdowns, date pickers) handle label associations?

Custom controls built with div, span, or other non-form elements do not inherit native label behavior. They require explicit ARIA roles (role=“combobox”, role=“listbox”) and accessible names via aria-label or aria-labelledby. A custom dropdown must have role=“combobox” and an aria-labelledby pointing to visible label text or an aria-label providing the name directly. The label element’s for attribute only works with native form elements — it has no effect on a div with a role attribute. Custom date pickers need aria-label on the trigger control and aria-live regions or role=“status” to announce selected values. Test custom controls with a screen reader as well, because an automated check sees only what the markup declares.

Which product detects this

Probeo checks this on every page of your site as part of Site Clarity. This page explains one instance; the product tells you every page that has it.