Describe the Bug
Every row-select checkbox in the admin list view renders with an empty accessible name, so screen-reader users hear only "checkbox, unchecked" with no indication of which row it selects. axe-core reports this as a critical label violation — 10 nodes per page (one per row).
Rendered markup, stock admin list view, any collection:
<input aria-label="" id="_R_26kjakuatpesneb6jd9etb_" type="checkbox">
Root cause (verified on main)
CheckboxInput hardcodes an empty aria-label and defers naming to aria-labelledby:
|
aria-label="" |
|
aria-labelledby={name} |
aria-label=""
aria-labelledby={name}
SelectRow renders it with no name and no label:
|
<CheckboxInput |
|
checked={Boolean(selected.get(rowData.id))} |
|
className={[baseClass, `${baseClass}__checkbox`].join(' ')} |
|
onToggle={() => setSelection(rowData.id)} |
|
variant="muted" |
|
/> |
|
) |
<CheckboxInput
checked={Boolean(selected.get(rowData.id))}
className={[baseClass, `${baseClass}__checkbox`].join(' ')}
onToggle={() => setSelection(rowData.id)}
variant="muted"
/>
With name undefined React omits aria-labelledby entirely, leaving only aria-label="". The explicit empty string is the crux — it actively suppresses the name rather than merely failing to supply one.
Link to the code that reproduces this issue
The permalinks above — this is stock admin markup, not configuration-dependent, so a scaffolded repo would only re-render Payload's own SelectRow/CheckboxInput. Both files are unchanged on main at 6981aca. Happy to push a create-payload-app -t blank repo if you'd still like one; it would be the default template with no changes.
Reproduction Steps
pnpx create-payload-app@latest -t blank
- Open
/admin/collections/<any-collection> with at least one row.
- Inspect a row-select checkbox — or run axe-core against the page.
- Observe
aria-label="", no aria-labelledby, and no accessible name.
Which area(s) are affected?
area: ui
Environment Info
payload: 3.84.1
@payloadcms/ui: 3.84.1
Next.js: 16.2.6
Node: 25
Browser: Chrome 141 (headless)
axe-core: 4.12.1
Root cause verified present in main at 6981aca.
Possible fix
Passing a per-row label from SelectRow — e.g. aria-label={Select ${title}} using the collection's useAsTitle — would resolve it. More broadly, CheckboxInput emitting aria-label="" unconditionally means any consumer that doesn't pass name produces an unnamed checkbox, so it may be worth only emitting the attribute when there's a value for it.
Happy to open a PR if the direction is welcome.
Describe the Bug
Every row-select checkbox in the admin list view renders with an empty accessible name, so screen-reader users hear only "checkbox, unchecked" with no indication of which row it selects. axe-core reports this as a
criticallabelviolation — 10 nodes per page (one per row).Rendered markup, stock admin list view, any collection:
Root cause (verified on
main)CheckboxInputhardcodes an emptyaria-labeland defers naming toaria-labelledby:payload/packages/ui/src/fields/Checkbox/Input.tsx
Lines 88 to 89 in 6981aca
SelectRowrenders it with nonameand nolabel:payload/packages/ui/src/elements/SelectRow/index.tsx
Lines 32 to 38 in 6981aca
With
nameundefined React omitsaria-labelledbyentirely, leaving onlyaria-label="". The explicit empty string is the crux — it actively suppresses the name rather than merely failing to supply one.Link to the code that reproduces this issue
The permalinks above — this is stock admin markup, not configuration-dependent, so a scaffolded repo would only re-render Payload's own
SelectRow/CheckboxInput. Both files are unchanged onmainat 6981aca. Happy to push acreate-payload-app -t blankrepo if you'd still like one; it would be the default template with no changes.Reproduction Steps
pnpx create-payload-app@latest -t blank/admin/collections/<any-collection>with at least one row.aria-label="", noaria-labelledby, and no accessible name.Which area(s) are affected?
area: ui
Environment Info
Root cause verified present in
mainat 6981aca.Possible fix
Passing a per-row label from
SelectRow— e.g.aria-label={Select ${title}}using the collection'suseAsTitle— would resolve it. More broadly,CheckboxInputemittingaria-label=""unconditionally means any consumer that doesn't passnameproduces an unnamed checkbox, so it may be worth only emitting the attribute when there's a value for it.Happy to open a PR if the direction is welcome.