A form can look correct, submit correctly, and contain valid HTML while still giving assistive technology the wrong name for a control.
I recently inspected a committed Blazor accessibility fix with exactly that shape. Several fields placed their helper prose inside the field's <label>. Visually, the layout was tidy. Semantically, every helpful sentence had joined the control's accessible name.
The focused lesson is simple: a label names a control; helper text describes it. Those are different contracts, and the markup should preserve the distinction.
The bug that pixels do not reveal
Consider this generalized Razor markup:
<label for="reference">
Reference
<input id="reference" />
<span class="hint">
Use the reference shown on the original document.
</span>
</label>
A sighted user sees a short label, a field, and a quieter line of guidance. That visual hierarchy does not determine the accessibility tree.
Text associated through the label contributes to the control's accessible name. A screen reader may therefore announce something closer to “Reference, use the reference shown on the original document” as the field name. Repeat that pattern with a longer warning or policy note and routine form navigation becomes noisy.
The HTML can still validate. A component test can still find the input. A screenshot can remain pixel-for-pixel unchanged. The defect lives in semantics, not appearance.
Name and description are separate contracts
The safer structure keeps the label concise and connects the explanatory text separately:
<div class="field">
<label for="reference">Reference</label>
<input id="reference"
aria-describedby="reference-hint" />
<span class="hint" id="reference-hint">
Use the reference shown on the original document.
</span>
</div>
Now the relationships are explicit:
-
forandidassociate the short name with the control; - the hint has its own unique identifier; and
-
aria-describedbyasks assistive technology to announce the hint as supporting context.
The expected experience is a concise name first, then the explanation. Users who navigate by form control can scan efficiently without losing the useful guidance.
Why this is more than an ARIA edit
Moving the hint changes the document structure. The wrapper may now need to carry layout and typography that the old label supplied through inheritance. Without that CSS work, the semantic fix can cause inputs to change size, weight, spacing, or alignment even though the intended visual design has not changed.
Interactive content deserves another look too. A link nested inside a label can compete with the label's activation behaviour. Moving helper content outside the label makes the click targets honest again: the label activates the control; the link behaves as a link.
That is the engineering trade-off. The corrected markup needs a wrapper, stable IDs, deliberate CSS, and a little more review effort. In return, the field has a predictable name, the guidance remains available, and interactive descendants no longer share a misleading click target.
Test the semantics, not only the markup
The strongest check inspects the browser's computed accessibility tree. It should assert that the control has the expected accessible name and a separate description. That tests the behaviour users actually receive.
Not every test environment exposes that tree. A source guard can still be a useful backstop if its limits are explicit. For example, it can reject a hint nested inside a label, require every hint to have an ID, and require a matching aria-describedby reference.
An absence-only source test has a familiar risk: a wrong directory or broken pattern can find nothing and pass. Give the guard a non-vacuous census so it fails when it has not inspected a credible amount of markup. Then mutation-check it: deliberately move one hint back into a label, remove one description reference, and point the scan at an empty-but-valid directory. Each mutation should make the test fail for the intended reason.
The committed test I inspected follows that defensive shape. I did not rerun it, and it is not equivalent to a live screen-reader or browser accessibility-tree test.
A practical review checklist
For each field with helper text, ask:
- Is the accessible name short and specific?
- Is explanatory prose outside the label?
- Does the hint have a unique ID referenced by the control?
- Did the wrapper preserve the intended layout and font context?
- Are any links or other interactive elements trapped inside a label?
- Does at least one test inspect computed accessibility semantics, or clearly document the limits of its structural proxy?
Accessibility bugs often survive because the pixels look reassuring. Treat the accessibility tree as a first-class output of the component, alongside the rendered page and submitted model.
Where in your forms might helpful prose be quietly promoted into a field name?
Top comments (0)