Skip to content

Admin list view: row-select checkboxes have no accessible name (aria-label="" with no aria-labelledby target) #17376

Description

@citydean

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

  1. pnpx create-payload-app@latest -t blank
  2. Open /admin/collections/<any-collection> with at least one row.
  3. Inspect a row-select checkbox — or run axe-core against the page.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: uiRelated to the admin panel.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions