payloadcms / payloadcms/payload

Admin dashboard: breadcrumb select's combobox input has no accessible name (axe label, critical)

Open
#18,140 2 comments 0 reactions 1 assignee View on GitHub

@JessRynkar is already working on this.

Since Sep 10, 2026.

area: accessibility area: ui Bug
Dominant language
TypeScript
Stars
44.8k
Forks
4.2k
Avg merge
2d 21h
Merged PRs (30d)
53

Description

Describe the Bug

On the default admin dashboard, the breadcrumb select's combobox input has no accessible name. axe reports label (critical). A screen-reader user reaching it hears no indication of what the control selects.

Measured on a blank install:

const inp = document.querySelector('.dashboard-breadcrumb-select input')
inp.id                               // "react-select-…-input"
inp.getAttribute('role')             // "combobox"
inp.getAttribute('aria-label')       // null
inp.getAttribute('aria-labelledby')  // null
document.querySelector(`label[for="${inp.id}"]`)  // null
inp.tabIndex                         // 0 — reachable, and unnamed

The control is the dashboard breadcrumb ReactSelect rendered by DashboardStepNav. Its call site passes no accessible name:

// @payloadcms/next/dist/views/Dashboard/Default/ModularDashboard/DashboardStepNav.js
<ReactSelect
  className="dashboard-breadcrumb-select"
  isClearable={false}
  isSearchable={false}
  onChange={handleChange}
  options
  placeholder={t("general:dashboard")}
  value={{ label: t("general:dashboard"), value: "dashboard" }}
/>

There is no aria-label, aria-labelledby, or inputId bound to a visible label. react-select accepts aria-label / aria-labelledby and forwards them to the input, so supplying one at this call site would give the control a name. Note the placeholder and value.label are not a substitute — neither becomes the input's accessible name.

This needs no collections, fields, or configuration; it is on the dashboard of every install. A sibling defect on the same control — the dropdown indicator being focusable while aria-hidden — is filed separately.

Link to the code that reproduces this issue

https://github.com/rjbashar/payload-a11y-repro

Reproduction Steps
  1. createdb payload_a11y_repro, then cp .env.example .env and set DATABASE_URI / PAYLOAD_SECRET
  2. pnpm install
  3. pnpm build && pnpm starta production build; see the README note, as pnpm dev does not hydrate the admin in this minimal app and the dashboard appears empty
  4. Create the first user:
    curl -X POST http://127.0.0.1:3210/api/users/first-register -H 'Content-Type: application/json' -d '{"email":"repro@example.test","password":"Repro-passw0rd!"}'
  5. Sign in at /admin/login, landing on /admin
  6. Run the console snippet above, or node verify.mjs from the repo

Restricted to this rule:

{ "id": "label", "impact": "critical", "nodes": 1, "targets": ["#react-select-_r_1_-input"] }
Which area(s) are affected?

area: ui, area: accessibility

Environment Info
payload                       3.85.2
@payloadcms/next              3.85.2
@payloadcms/db-postgres       3.85.2
@payloadcms/richtext-lexical  3.85.2
next                          16.3.4
react / react-dom             19.2.6
react-select                  5.9.0  (transitive, via @payloadcms/ui)
node                          26.0.0
axe-core                      4.13.0
database                      PostgreSQL

Versions are pinned and the lockfile is committed, so the reproduction does not drift with @latest.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.