A cart page renders one row per item, and each row contains <label for="qty">Quantity</label><input id="qty">. What breaks, and how would you fix the labelling?
answer
- ids are document-unique, not row-scoped
- first match in tree order wins
- the loop is where it breaks
- wrapping needs no ids at all
- nine identical names identify nothing
basics
~20 sThe id is repeated, and ids must be unique in a document. Every label resolves to the first matching input, so clicking any row's label focuses row one and later inputs end up with no associated label at all.
solid answer
~50 sRepeating `id="qty"` breaks the document-unique id rule, and `for` resolves to the first element in tree order with that id. So every "Quantity" label points at row one's input: clicking row three's label focuses row one, and rows two onward have no associated label. Nothing errors, so it survives review and demos. Two fixes. Make the ids unique per row — derive them from the item's stable identifier, `id="qty-sku-4821"`, not from a render-time counter that changes when the list reorders. Or drop ids entirely and wrap: `<label>Quantity <input name="qty"></label>`, which associates implicitly and cannot collide, which is why it suits generated lists. Then fix the second, subtler defect: nine controls all named "Quantity" are correctly labelled and still useless out of context, because a screen-reader user listing the form's fields hears the same name nine times. Put the distinguishing text in the label — "Quantity, Blue T-shirt" — with the item part visually hidden if the design has no room.
code
html · 11 lines<!-- Broken: the id repeats, so every label points at the first input -->
<label for="qty">Quantity</label>
<input type="number" id="qty" name="qty">
<label for="qty">Quantity</label>
<input type="number" id="qty" name="qty">
<!-- Fixed: wrapping needs no ids, and the label distinguishes its row -->
<label>
Quantity <span class="visually-hidden">for Blue T-shirt, medium</span>
<input type="number" name="items[4821][qty]">
</label>go deeper
Know that ids must be unique in a document and that for resolves to the first match, so repeating the same id across rows sends every label to the first control.
Explain both remedies — ids derived from stable item data, or wrapping the input in its label so no id exists — and say which you would choose in generated markup and why.
Go past the bug to the pattern: identical names across rows are still unusable, so labels must distinguish their row. Describe how you would detect this across rendered pages instead of fixing it page by page.
Own the systemic answer — a field component that either wraps or generates collision-free ids, plus an automated check on rendered output — so no team has to remember the rule during review.
## The rule being broken An `id` must be unique within the document. `for` performs a lookup by id and takes the **first** match in tree order — there is no scoping to the nearest row, no error, no console warning. So in a list of nine cart rows all using `id="qty"`: - All nine labels associate with row one's input. - Clicking row seven's "Quantity" text focuses row one's box — a visible bug that testers usually catch eventually. - Rows two through nine have **no** associated label. Their inputs are announced by role alone: "edit text", nine times. This is one of the most common labelling defects in real applications, because it appears the moment static markup is turned into a loop, and the single-row case that was hand-authored and reviewed was correct. ## Fix one: unique ids ```html <label for="qty-sku-4821">Quantity</label> <input id="qty-sku-4821" name="items[4821][qty]" type="number"> ``` Derive the suffix from something stable about the item — a SKU, a line-item identifier — rather than from the loop index. Index-based ids are correct at first render and become misleading when the list reorders or an item is removed, and if two independent components on the page both use `qty-0` you are back to a collision. Also remember the id must be unique across the *whole document*, not just the widget: a sidebar, a modal, and an embedded partial all share one id namespace. ## Fix two: wrap instead ```html <label> Quantity <input type="number" name="items[4821][qty]"> </label> ``` Implicit association binds the label to its first labelable descendant. No ids, therefore no collisions, therefore nothing to keep unique as the list grows or reorders. For generated repeated markup this is usually the more robust choice, and its only real constraint is structural: the control must live inside the label element, which occasionally conflicts with a layout that needs them in separate containers. ## The defect underneath the defect Suppose you fix the ids. Every input is now correctly labelled — and a screen-reader user pulling up a list of the page's form fields hears "Quantity, Quantity, Quantity, Quantity…" with nothing to tell them apart. Correct labelling is not the same as *useful* labelling: a name has to identify the control, and in repeated markup a name that is identical across every instance identifies nothing. The markup-level fix is to put the distinguishing text inside the label: ```html <label> Quantity <span class="visually-hidden">for Blue T-shirt, medium</span> <input type="number" name="items[4821][qty]"> </label> ``` The span is part of the label's text content, so it is part of the name, and the class positions it off-screen so the visible column still reads simply "Quantity". The same reasoning applies to any repeated control in a list: "Remove", "Edit", and "Select" buttons in a table of rows are the identical problem wearing different clothes. ## Why this ships Three reasons worth naming, because an interviewer is often probing your review instincts rather than the fix itself. Nothing throws — duplicate ids are recovered from silently. It looks correct visually, since every row shows the word "Quantity" beside a box. And it passes a one-row test fixture; the bug only exists at length two and above. So the review habit that catches it is specific: **whenever markup containing an `id` moves inside a loop, that id is a bug until it is made unique or removed.** That single rule catches this, catches the repeated-`for` variant, and catches the same defect in any other id-dependent association. ## What to say "Ids are document-unique, so `for` resolves to the first match — every label points at row one, and every row after the first is unlabelled. I'd wrap the input in its label so no ids are involved, and I'd also make each label distinguish its row, because nine controls named 'Quantity' are technically labelled and practically useless."
- Why prefer an id derived from the item's identifier over one derived from the loop index?Index-based ids are correct at first render and go wrong afterwards: reordering or removing an item shifts every suffix, and two independent components on the same page can both emit `qty-0`. An id from a stable identifier — a SKU or line-item id — stays correct across reorders and will not collide with an unrelated widget elsewhere in the document.
- How would you find this defect across an existing application rather than one page at a time?Check rendered output, not source templates, since the duplication only exists after the loop runs. An automated accessibility scan over real pages flags both duplicate ids and controls with no accessible name. Then add the review rule: any id inside repeated markup is a defect unless it is derived from item data or removed by wrapping.
- Everything is fixed, but every row's control is still named "Quantity". Is that acceptable?It is valid and still poor. A user listing the form's controls hears the same name repeatedly with nothing to choose between them. Put the distinguishing text inside the label — "Quantity for Blue T-shirt" — with the item portion visually hidden so the column still reads "Quantity". The same applies to repeated "Remove" or "Edit" controls.
saying these in an interview costs you the question
- Duplicate ids are fine as long as rows look right
- The browser scopes the for lookup to the nearest row
- Only the last duplicated id wins the association
- Identical labels on every row are good enough
- A one-row test proves the labelling is correct