What happens
Dropdown puts id, aria-label and aria-labelledby on Select.Root
(dist/esm/Dropdown/Dropdown.js):
<Select.Root … id={id} name={name} tabIndex={tabIndex}
aria-label={ariaLabel} aria-labelledby={ariaLabelledby} … >
<Select.Trigger>
<Select.Value placeholder={placeholder} />
…
Select.Root renders as a plain <div data-part="root"> carrying no role. The focusable element is the inner
<button data-part="trigger" role="combobox">, and it receives none of them.
So the combobox has no accessible name, and an external <label for={id}> cannot name it either, because a
<div> is not a labelable element.
Evidence
@cratis/components 3.4.0, primereact 11.1.0.
Before the workaround, the rendered tree was:
<div data-part="root" aria-label="Select a role">
<button data-part="trigger" role="combobox"> <!-- aria-label: null -->
Testing Library reported combobox: Name "".
After routing both attributes through pt={{ trigger: { id, 'aria-label': … } }}, the same query reported
combobox: Name "Advisory role".
What it costs a consumer
High. It is an unnamed control for screen-reader users on every dropdown in the application — the failure is
invisible to a type-checker, a linter and a rendering unit test, and visible only to an accessibility audit or a
screen reader.
Our workaround: our own Select wrapper sets id, aria-label, aria-describedby and aria-invalid through
pt.trigger, and deliberately does not pass id to Dropdown, because otherwise two elements claim the
same id.
Suggested fix — the seam
Forward id / aria-label / aria-labelledby / aria-describedby to the trigger button rather than to
Select.Root. The wrapper is not the control.
What is explicitly not being asked for
Not asking for any change to the Select.* composition itself, and not asking Components to invent an
accessible name when the consumer supplies none. Only that the labelling props the component already accepts
land on the element that carries role="combobox".
What happens
Dropdownputsid,aria-labelandaria-labelledbyonSelect.Root(
dist/esm/Dropdown/Dropdown.js):Select.Rootrenders as a plain<div data-part="root">carrying no role. The focusable element is the inner<button data-part="trigger" role="combobox">, and it receives none of them.So the combobox has no accessible name, and an external
<label for={id}>cannot name it either, because a<div>is not a labelable element.Evidence
@cratis/components3.4.0,primereact11.1.0.Before the workaround, the rendered tree was:
Testing Library reported
combobox: Name "".After routing both attributes through
pt={{ trigger: { id, 'aria-label': … } }}, the same query reportedcombobox: Name "Advisory role".What it costs a consumer
High. It is an unnamed control for screen-reader users on every dropdown in the application — the failure is
invisible to a type-checker, a linter and a rendering unit test, and visible only to an accessibility audit or a
screen reader.
Our workaround: our own
Selectwrapper setsid,aria-label,aria-describedbyandaria-invalidthroughpt.trigger, and deliberately does not passidtoDropdown, because otherwise two elements claim thesame id.
Suggested fix — the seam
Forward
id/aria-label/aria-labelledby/aria-describedbyto the trigger button rather than toSelect.Root. The wrapper is not the control.What is explicitly not being asked for
Not asking for any change to the
Select.*composition itself, and not asking Components to invent anaccessible name when the consumer supplies none. Only that the labelling props the component already accepts
land on the element that carries
role="combobox".