payloadcms / payloadcms/payload
Admin dashboard: dropdown indicator is focusable while aria-hidden (axe aria-hidden-focus, serious)
@JessRynkar is already working on this.
Since Sep 10, 2026.
- 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 dropdown indicator is a focusable <button> carrying aria-hidden="true". axe reports aria-hidden-focus (serious), and the element genuinely takes keyboard focus, so a keyboard or screen-reader user can land on a control that is hidden from the accessibility tree.
Rendered node on a blank install:
<button class="dropdown-indicator" aria-hidden="true" type="button">
<svg class="icon icon--chevron dropdown-indicator__icon" …>
</button>
Measured, not inferred: tabIndex is 0, and calling .focus() on it results in document.activeElement === btn (focusReceived: true).
The control is the dashboard breadcrumb ReactSelect rendered by DashboardStepNav (@payloadcms/next/dist/views/Dashboard/Default/ModularDashboard/DashboardStepNav.js, rendered from ModularDashboard/index.client.js). It needs no collections, fields, or configuration — it is on the dashboard of every install.
A likely mechanism, offered as a hypothesis rather than a finding. @payloadcms/ui's DropdownIndicator renders a <button> and spreads react-select's innerProps onto it:
// @payloadcms/ui/dist/elements/ReactSelect/DropdownIndicator/index.js
export const DropdownIndicator = props => {
const { innerProps: { ref, ...restInnerProps } } = props;
return _jsx("button", { className: baseClass, ref, ...restInnerProps, … });
react-select supplies aria-hidden: 'true' in innerProps for its indicators, which is correct for its own default non-focusable <div> and becomes a violation once those props land on a focusable <button>. I have verified the rendered result and the focus behaviour; I have not verified that this spread is the source, so please treat the attribution as a starting point rather than a diagnosis.
A sibling defect on the same control is filed separately (the combobox input has no accessible name).
Link to the code that reproduces this issue
https://github.com/rjbashar/payload-a11y-repro
Reproduction Steps
createdb payload_a11y_repro, thencp .env.example .envand setDATABASE_URI/PAYLOAD_SECRETpnpm installpnpm build && pnpm start— a production build; see the README note, aspnpm devdoes not hydrate the admin in this minimal app and the dashboard appears empty- 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!"}' - Sign in at
/admin/login, landing on/admin - In the console:
const b = document.querySelector('.dashboard-breadcrumb-select button.dropdown-indicator') b.outerHTML // aria-hidden="true" b.tabIndex // 0 b.focus(); document.activeElement === b // true
verify.mjs in the repo performs all of this headlessly and prints the axe result. Restricted to this rule:
{ "id": "aria-hidden-focus", "impact": "serious", "nodes": 1, "targets": [".dropdown-indicator"] }
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.