Skip to content
academia.sh

Lesson 22 / 25

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.

Contents

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.

<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.

<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.

// 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.

<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.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close