Selector: isLabelHidden hides the label without associating it — trigger has no accessible name
Bug: Selector with isLabelHidden hides the label without associating it — the trigger has no accessible name
Package: @astryxdesign/[email protected]
Component: Selector
What happens
<Selector label="Project" isLabelHidden ... /> renders its trigger as:
<button role="combobox" id="_r_gp_" aria-controls="..." aria-expanded="false" aria-haspopup="listbox" tabindex="0">
No project
</button>There is no aria-label and no aria-labelledby pointing back at the visually-hidden label. The trigger's accessible name falls back to its own text content — which is the currently selected value, not the field's label.
Why this is a real-world problem, not a nitpick
In an app with many Selector instances on a list (one per row), every trigger with the same current value announces as the same accessible name. Concretely: a to-do list with a "Project" selector on every row, where 122 rows have no project selected, produced 122 buttons all named "No project" to a screen reader — indistinguishable from one another. A "You" default-assignee selector on 118 rows had the same problem.
Passing a more descriptive label does not help, because label is never wired to aria-label/aria-labelledby on the trigger when isLabelHidden is set — only the sighted-visible label element disappears.
Repro
import { Selector } from "@astryxdesign/core/Selector";
<Selector
label="Project"
isLabelHidden
value={UNSET}
onChange={() => {}}
options={[{ value: UNSET, label: "No project" }, { value: "p1", label: "Alpha" }]}
/>Inspect the rendered <button role="combobox">: no aria-label, no aria-labelledby.
Expected
When isLabelHidden is set, the label should still be associated with the trigger — either:
- render the label element visually-hidden (e.g.
sr-only) and setaria-labelledbyto its id, or - set
aria-label={label}on the trigger directly.
Either would make label do what its name implies even when hidden from sight.
Current workaround
Pass an explicit aria-label prop through Selector's prop spread (it does forward unclaimed props to the trigger, so this works today):
<Selector label="Project" aria-label={`Project — ${row.title}`} isLabelHidden ... />This works, but it means label is effectively decorative/ignored whenever isLabelHidden is true, which isn't documented and is easy to miss — we only found it by inspecting the live DOM.
Version
@astryxdesign/[email protected]
Source: facebook/astryx