---
title: 'Label-Field Relationship'
source: 'https://academia.sh/en/courses/web-fundamentals-and-html/label-field-relationship'
course: 'Web Fundamentals and HTML'
language: en
updated: '2026-08-17T18:09:32+00:00'
license: 'CC BY-SA 4.0'
---

# Label-Field Relationship

Explicit and implicit binding of a label to a field, the distinction between the id and name attributes, auditing cases where the bond is missing, and the limit of the placeholder.

The fields are declared, but what each one wants is understood only from the text next to it.
Until this text is declared in the document as bound to the field, the field is **nameless**.

A nameless field has three concrete consequences. Clicking the label does not move focus to
the field — a convenience that, for small checkboxes, multiplies the clickable area several
times over. A screen reader announces the field only by its type: "editable text." And a
voice-control user cannot refer to the field by name.

## Two Ways to Bind

**Explicit bond**: the `label` element's `for` attribute names the field's `id` value.

```html
<p><label for="station">Station code</label>
   <input id="station" name="station_code" type="text"></p>
```

**Implicit bond**: the `label` element wraps the field; `for` and `id` are not needed.

```html
<p><label>Temperature <input name="temperature" type="number"></label></p>
```

Both ways are valid. The explicit bond allows the label and the field to sit in different
places in the document — a label in a table's header cell bound to a field in a body cell can
only be done this way. The implicit bond is shorter and needs no id, but requires the wrapped
field to stay inside the label.

A field can have more than one label; its accessible name is made up of all of their text. A
label, however, can have only one field.

## Id and Name Are Separate Things

The two attributes are often confused, and the confusion produces a silent flaw.

`id` is the element's identity **within the document**. It must be unique in the document.
Label binding, url fragments, and declarations like `aria-describedby` use it.

`name` is the field's name **in submission**. It does not need to be unique in the document —
radio button groups share the same name. This is the name the server sees.

The two can carry the same value, but they are not the same thing. The script below scans a
form and finds each field's label source.

```js
// label-audit.mjs — auditing the label-field bond
const form = `
<form action="/record" method="post">
  <p><label for="station">Station code</label>
     <input id="station" name="station_code" type="text"></p>
  <p><label>Temperature <input name="temperature" type="number"></label></p>
  <p><label for="humidity">Relative humidity</label>
     <input id="relative-humidity" name="humidity" type="number"></p>
  <p><input name="note" type="text" placeholder="Description"></p>
  <p><input name="hidden" type="hidden" value="north-slope"></p>
</form>`;

const explicitBonds = new Map();
for (const m of form.matchAll(/<label for="([^"]+)">([^<]*)<\/label>/g)) {
  explicitBonds.set(m[1], m[2].trim());
}
const implicitBonds = new Map();
for (const m of form.matchAll(/<label>([^<]*)<input([^>]*)>[^<]*<\/label>/g)) {
  const id = m[2].match(/id="([^"]*)"/)?.[1] ?? null;
  const name = m[2].match(/name="([^"]*)"/)?.[1] ?? null;
  implicitBonds.set(id ?? "name:" + name, m[1].trim());
}

console.log("field (name)     id                label source     accessible name");
for (const m of form.matchAll(/<input([^>]*)>/g)) {
  const attrs = m[1];
  const name = attrs.match(/name="([^"]*)"/)?.[1] ?? "";
  const id = attrs.match(/id="([^"]*)"/)?.[1] ?? "";
  const type = attrs.match(/type="([^"]*)"/)?.[1] ?? "text";
  if (type === "hidden") {
    console.log(name.padEnd(18) + id.padEnd(18) + "not needed".padEnd(17) + "(hidden field)");
    continue;
  }
  let source = "NONE";
  let labelText = "";
  if (id && explicitBonds.has(id)) { source = "for/id"; labelText = explicitBonds.get(id); }
  else if (implicitBonds.has(id) || implicitBonds.has("name:" + name)) {
    source = "wrapping label";
    labelText = implicitBonds.get(id) ?? implicitBonds.get("name:" + name);
  }
  console.log(name.padEnd(18) + (id || "—").padEnd(18) + source.padEnd(17) + (labelText || "—"));
}

console.log("--- for values that fall through ---");
const ids = new Set([...form.matchAll(/id="([^"]*)"/g)].map((m) => m[1]));
for (const target of explicitBonds.keys()) {
  if (!ids.has(target)) console.log('for="' + target + '" -> no such id');
}
```

```
field (name)     id                label source     accessible name
station_code      station           for/id           Station code
temperature       —                 wrapping label   Temperature
humidity          relative-humidity NONE             —
note              —                 NONE             —
hidden                              not needed       (hidden field)
--- for values that fall through ---
for="humidity" -> no such id
```

The third row is the typical shape of an id-name mix-up. The label has `for="humidity"`
written on it, and the field's `name` value is also `humidity`; but its `id` value is
`relative-humidity`. Since the bond is built through `id`, it is never built at all. Nothing
looks wrong in the document: the label text stays put, the field works, the form submits.
Only the name is lost.

The first row shows the opposite: `id` and `name` can carry different values, and that is not
a problem. The bond is built through `id`, and `name` is used in submission.

The fourth row is a field with no label at all — a `placeholder` stands in its place instead.

The fifth row shows that hidden fields need no label: they are never presented to the user.

## A Placeholder Is Not a Label

The `placeholder` attribute is text that appears inside the field and disappears once typing
begins. It cannot substitute for a label, and there is more than one reason.

The text disappears the moment the field is filled; when the user wants to check what was
asked, there is nowhere left to look. If the form is long, or the user paused and came back,
this is a concrete loss.

Placeholder text is shown at low contrast in browser defaults; it is hard to read for users
with limited vision, and raising the contrast makes it get mistaken for an entered value.

In some screen reader and browser combinations, the placeholder is announced as the
accessible name; in others it is not — the behavior is inconsistent.

The right use of a placeholder is to give a **format example** in addition to the label: while
the label says "Measurement time," the placeholder can show `07:30`.

## What Label Text Should Say

The label says what the field **wants**, not how to fill it in. If "Name" is enough, "Type
your name here" is not written: a screen reader re-announces this text on every focus, and
long labels make the form tiring.

Extra marks added to label text are announced too. Marking required fields with an asterisk is
common; what the asterisk means must be explained at the top of the form, and the requirement
must also be declared through the field's own attribute. A visual mark is not a declaration.

Unit information can be carried in the label — as in "Measured value (°C)" — or left to the
description. If the unit is necessary to interpret the value, it is preferred in the label;
some modes skip the description, none ever skip the name.

Whether the label is written before or after the field can look like a presentation choice,
but source order determines reading order. For checkboxes and radio buttons the label is
written after the field, for other fields before it; this makes sure the state of the control
is announced in order with what it is for.

## Description

Help text about a field, unit information, or a format rule is bound with
`aria-describedby`. The label gives a name; the description gives extra information and is
announced after the name on a screen reader.

```html
<p>
  <label for="value">Measured value</label>
  <input id="value" name="value" type="number" step="0.1"
         aria-describedby="value-help">
  <span id="value-help">Degrees Celsius, period as the decimal separator.</span>
</p>
```

The attribute can take more than one id separated by spaces; the texts are joined in order.
This will be used in the next lesson to bind an error message to a field.

## Summary

- A label is bound to a field explicitly with `for`/`id` or implicitly by wrapping; if no bond
  is built, the field stays nameless and clicking, announcing, and voice-control behavior all
  break.
- `id` is the identity within the document and must be unique; `name` is the name in
  submission and can repeat. When the two are confused, the bond silently fails to form.
- A field can have more than one label; a label can have only one field.
- `placeholder` does not substitute for a label: it disappears once filled, is shown at low
  contrast, and is not consistently announced as the name.
- Help text is bound with `aria-describedby` and announced after the name.

## Next Step

The fields are named; next comes whether the value entered is accepted. Before submission, the
browser tests the constraints declared, and stops submission if any field is invalid. The next
lesson looks at these constraints, the validity states they produce, and why the same check
needs to be redone on the server.
