Checklist
- Use
<fieldset>+<legend>for groups of radios/checkboxes so the group question is announced with the options. - Keep the legend meaningful (it’s the group label, not decoration).
- Connect hint text with
aria-describedbyon the<fieldset>(or on each control if the hint is per-option). - Use
role="group"+aria-labelledbyonly when you can’t use<fieldset>(e.g., a grouping that isn’t a form fieldset).
Minimal pattern
<fieldset aria-describedby="shipHint">
<legend>Shipping speed</legend>
<div id="shipHint">Choose one.</div>
<label><input type="radio" name="ship" value="standard"> Standard</label>
<label><input type="radio" name="ship" value="express"> Express</label>
</fieldset>
If you truly can’t use a fieldset (or your group isn’t a form fieldset), use a container with a label:
<div role="group" aria-labelledby="q1" aria-describedby="q1hint">
<div id="q1">Notification preferences</div>
<div id="q1hint">Optional.</div>
...controls...
</div>
Pitfalls
- Div-only grouping: a heading sitting above a set of options doesn’t automatically become the group label.
- Hiding the legend: if you visually hide it, ensure it’s still present and meaningful (don’t replace it with placeholder text).
- Placeholder-as-legend: placeholders disappear as you type and are not a reliable label.
- Repeated option labels: “Yes/No” repeated across multiple questions becomes ambiguous without a group label.
- Overusing
role="group": prefer native semantics; reserverole="group"for edge cases.