Command Palette

Search for a command to run...

Hectal
PHASE 7Advanced ~27 min· topic 5 of 8

Topic 7.5

Accessible Names and Forms

In one line

Every control has an accessible name that the browser computes in a fixed order (aria-labelledby, aria-label, the native label, the content, then title or placeholder); accessible forms pair that with visible labels, required and invalid states, and error messages linked by aria-describedby.

Think of it like this

Labels on a spice rack. Every jar needs a label, or you're tasting each one to find the cumin. If a jar has a printed label and a sticky note and handwriting on the lid, you need a rule for which one counts: the sticky note on top wins. Browsers have exactly such a rule for naming every button, link and field.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Accessible name
The short text that identifies an element to assistive tech, like "Email" or "Subscribe".
Accessible description
Extra text read after the name and role, such as a hint or an error.
accname
The W3C spec with the exact rules browsers follow to compute names and descriptions.
Name from content
Getting the name from the text inside the element. Buttons, links and headings do this; divs don't.
Placeholder
Grey example text shown inside an empty field. It disappears when you type and is not a label.
fieldset and legend
A box that groups related fields, and its caption, which names the group.
aria-invalid
Says a field's value failed validation. Set it to "true" only after checking.
Error summary
A box at the top of a form listing every error, each linked to its field.
Label in Name
WCAG rule 2.5.3: the accessible name must include the text you can see on the control.

Step by step

01Follow the name order

For any element, the browser walks this list and stops at the first non-empty answer. That's why an aria-label silently replaces the visible text of a button, and why aria-labelledby beats everything.

Follow the name orderdiagram
Rendering diagram…

02Label every field visibly

Explicit labels (for matching id) work everywhere and let you put the label anywhere. Wrapping labels need no ids. Both make the label text clickable.

form.htmlwhole filehtml
<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email">

<label>
  <input type="checkbox" name="news"> Send me the newsletter
</label>

03Name groups with fieldset and legend

The legend becomes the group's name and is announced when focus enters the group.

form.htmlwhole filehtml
<fieldset>
  <legend>Delivery speed</legend>
  <label><input type="radio" name="speed" value="std" checked> Standard</label>
  <label><input type="radio" name="speed" value="exp"> Express</label>
</fieldset>

04Add hints and mark required fields

A hint goes in its own element, linked with aria-describedby. required is announced and enforced. If you style required fields with an asterisk, explain it once at the top.

form.htmlwhole filehtml
<p>Fields marked * are required.</p>
<label for="pw">Password *</label>
<input id="pw" name="pw" type="password" required
       aria-describedby="pw-hint" autocomplete="new-password">
<p id="pw-hint">At least 12 characters.</p>

05Show errors the accessible way

After the user submits, mark the field invalid, put the message in text next to it, link the message, and move focus. aria-describedby can list several ids, so the hint and the error are both read.

form.htmlwhole filehtml
<label for="pw">Password *</label>
<input id="pw" name="pw" type="password" required
       aria-invalid="true" aria-describedby="pw-error pw-hint">
<p id="pw-error" class="error">Error: Password must be at least 12 characters. You entered 7.</p>
<p id="pw-hint">At least 12 characters.</p>

06Hear the result

When focus moves to the field, a screen reader says something like: "Password star, edit text, required, invalid entry, Error: Password must be at least 12 characters. You entered 7. At least 12 characters." Every part came from the HTML you wrote.

Try it yourself

  1. 1

    Predict the winner

    In the first example, add aria-label="Your email" to input i2. Predict its name before running. (The aria-label wins over the label. "Your email" still contains the visible word "Email", so it passes Label in Name, which ignores letter case; "Contact address" would not.)

  2. 2

    Name the empty button

    Give button b3 a name two different ways: first aria-label="Settings", then instead put <span class="visually-hidden">Settings</span> inside it. Run after each change.

  3. 3

    Check the real tree

    Right-click the email field in the second example's preview → Inspect → open the Accessibility pane. Find the Name ("Email *"), the Description (the error text after a failed submit) and the invalid state.

Code & diagrams

Which name wins? New tab

This is a teaching version of the real algorithm. Check the real names in DevTools: Elements → Accessibility pane → Computed Properties → Name, which also shows where each name came from. Note b2: the visible "Save" is still inside the name "Save draft", so it passes Label in Name.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

b1 -> Save  (content)
b2 -> Save draft  (aria-label)
i1 -> Billing address
i2 -> Email  (label)
i3 -> Search  (placeholder fallback)
l1 -> Home  (content)
b3 -> NO NAME
Accessible error messages New tab

`novalidate` turns off the browser's own bubbles so we control the messages; the `validity` object still does the checking. The message starts with "Error:" so it isn't conveyed by red colour alone.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

invalid: true | focused: true | message: Error: Enter your email address.
invalid: true | focused: true | message: Error: Enter an email like name@example.com.
invalid: false | focused: true | message: (none)

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use a placeholder instead of a label

Replace <label for="email">Email</label><input id="email"> with <input placeholder="Email">.

terminal
$ Type in the field, then look again
── what you'll see ──
The hint disappears while typing; on review the user can't tell what the field was. Axe: "Form elements must have labels" (placeholder alone doesn't count)

Break #2

Name a button differently from its text

<button aria-label="Submit the form">Send</button>

terminal
$ voice control: "click Send"
── what you'll see ──
Nothing happens: no control is named "Send"

Break #3

Set aria-invalid before the user has typed

Ship the form with aria-invalid="true" on every empty required field.

terminal
$ screen reader entering the form
── what you'll see ──
"Name, edit text, required, invalid entry" on a field the user hasn't touched yet

Myth vs fact

Myth

A placeholder is a label.

Fact

It's a hint that disappears. It counts only as a weak fallback name, and WCAG and every design system recommend a visible label.

Myth

aria-label adds to the visible text.

Fact

It replaces it as the name. Screen readers read the aria-label, not the text inside.

Myth

Red borders are enough to show an error.

Fact

Colour alone fails WCAG 1.4.1 and is invisible to screen readers. Add text, and link it with aria-describedby.

Myth

The title attribute is a good way to label a field.

Fact

It is the last fallback, appears only on mouse hover, and isn't shown on touch devices. Use a label.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Test frameworks (Testing Library's getByRole("button", { name: "Save" }), Playwright's getByRole) query the accessible name, so good names make tests sturdier than CSS selectors. dom-accessibility-api implements accname for jsdom-based tests.

  • ▸

    aria-labelledby and aria-describedby can reference hidden elements (with hidden or display: none) and still use their text. That's how a visually hidden label element can name a control. They can also reference the element itself: aria-labelledby="btn-id product-name" gives "Buy, Blue mug".

  • ▸

    aria-errormessage (ARIA 1.1+) points to an error message and is only exposed when aria-invalid="true". Screen reader support has improved but is still less consistent than aria-describedby, so most production forms (including the GOV.UK Design System) use aria-describedby for errors.

  • ▸

    CSS :user-invalid (all major browsers since 2023) matches a field only after the user has interacted with it, which avoids showing errors on untouched fields. Pair it with aria-invalid set from script, because CSS state isn't exposed to assistive tech.

Remember this

  1. 1

    The accessible name is the short text that identifies an element to assistive tech: what a screen reader says ("Email, edit text"), what voice control users say ("click Subscribe") and what automated tests search for (getByRole("button", { name: "Subscribe" })). The accessible description is extra, optional information read after it. Both are computed by the browser following the W3C Accessible Name and Description Computation spec ("accname") plus the HTML Accessibility API Mappings.

  2. 2

    The order, simplified, first match wins: 1. aria-labelledby: the text of the referenced elements, joined by spaces (it even works when they are hidden). 2. aria-label. 3. The native label from HTML: a <label> for form fields, alt for images, <legend> for a fieldset, <caption> for a table, <figcaption> for a figure, the value of <input type="submit">. 4. Name from content: for roles like button, link, heading, tab and cell, the text inside, including the alt of images inside. 5. A last-resort fallback: title, and for text fields placeholder. If all are empty, the element has no name, which for an interactive element is a WCAG 4.1.2 failure.

  3. 3

    Name from content only applies to certain roles. Buttons, links, headings, menu items, options and table cells get their name from what's inside. A div, section or form does not: putting text inside a region doesn't name it; you need aria-labelledby pointing at its heading.

  4. 4

    Forms need visible labels. A <label for="id"> (or a label wrapping the field) names the field, and clicking the label focuses or toggles it, which gives a bigger click target. Placeholder is not a label: it disappears as soon as you type, often has low contrast, and is only a fallback name. WCAG 2.5.3 Label in Name (A) says the accessible name must contain the visible label text, so voice users can say what they see: a button showing "Send" must not be named "Submit form".

  5. 5

    Groups need names too. Radio buttons and related checkboxes go in a <fieldset> with a <legend>: a screen reader says "Delivery speed, grouping" and then "Express, radio button, 1 of 2". Without the legend, the user hears "Express, radio button" and has to guess the question.

  6. 6

    Required and invalid. The required attribute is exposed as "required" to assistive tech and also triggers browser validation (Topic 4.7); show it visually too (the word "required", or an asterisk explained at the top of the form). After validation fails, set aria-invalid="true" on the field (not before the user has tried), write the error as text next to the field (not colour alone: WCAG 1.4.1), and link it with aria-describedby="email-error" so it's read with the field. On submit, move focus to the first invalid field or to an error summary that links to each field. WCAG 3.3.1 Error Identification (A) and 3.3.3 Error Suggestion (AA) ask you to say what's wrong and how to fix it.

Explain it without notes

01

Walk through how a browser computes the accessible name of a button or input.

02

Why isn't placeholder text a replacement for a label?

03

How should a form announce a validation error to a screen reader user?

04

What is WCAG 2.5.3 Label in Name, and how does aria-label commonly break it?

Practice

01

Write an accessible "Choose a plan" group with three radio options (Free, Pro, Team), with Free selected.

02

Write a phone field with a visible label, a hint "Include your country code", required, and an error message slot, all correctly linked. Show the HTML after a failed submit.

03

Fix this: <input type="search" placeholder="Search"><button><img src="search.svg"></button>.

Trade-offs

  • ↔

    Inline validation while typing gives fast feedback but can announce errors before the user has finished; validating on submit and on leaving a field is calmer and is what most accessible design systems do.

  • ↔

    An error summary plus inline messages is more work than inline messages alone, but on long forms it lets users see every problem at once and jump to each field.

Done when you can

  • I can list the accessible name order and predict which source wins.

  • I give every field a visible label and every radio group a legend.

  • I link hints and errors with aria-describedby.

  • I set aria-invalid only after validation and move focus to the problem.

  • I keep the visible label inside the accessible name.

  • I can read the name, description and states in the DevTools Accessibility pane.