#6318·astryx

Selector: isLabelHidden hides the label without associating it — trigger has no accessible name

Author: projectlogos-inCreated Sep 14, 2026Updated Sep 14, 2026

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:

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

typescript
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 set aria-labelledby to 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):

typescript
<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]