Opened 5 weeks ago
Last modified 4 weeks ago
#65793 new defect (bug)
Clean up the accessible name of menu and admin bar items with counters
| Reported by: | afercia | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | 7.2 |
| Component: | Administration | Version: | |
| Severity: | normal | Keywords: | has-screenshots has-patch has-unit-tests |
| Cc: | Focuses: | accessibility |
Description
In the admin menu and in the admin bar, some items report a count that is visually presented with a number within a circle. See attached scdeenshot.
While the visual presentation is similar, the actual accessible name (the text content) of these items is inconsistent and can't be operated with voice control.
- The visible label doesn't match the actual text content.
- The visually hidden text of the counters is inconsistent.
Some examples of the actual text content:
In the admin menu:
- Updates 2
- Comments 3 Comments in moderation
- Themes 1
- Plugins 1
In the admin bar:
- 2 updates available
- 3 Comments in moderation
While this has been already reported in broader tickets related to the menus, this ticket aims to improve specifically these links labels.
I'd like to propose:
Standardize the text content of all these links to the name of the item, e;g;:
- Updates
- Comments
- Themes
- Plugins
Move the information about pending updates, comments in moderation etx to a visually hidden element referenced by aria-describedby.
This way:
- All the menu items accessible names would be simpliied and consistent.
- Voice control users would be able to activate the links with a voice command, as the visibel text (excluding the counter) would match the accessible name.
Attachments (1)
Change History (8)
This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.
5 weeks ago
This ticket was mentioned in PR #12850 on WordPress/wordpress-develop by @sanket.parmar.
5 weeks ago
#3
- Keywords has-patch has-unit-tests added
#4
@
5 weeks ago
I put up a proof-of-concept PR for this: https://github.com/WordPress/wordpress-develop/pull/12850
It follows the approach from the description — standardize every counter (Updates, Comments, Themes, Plugins, Site Health, plus the toolbar Comments/Updates) so the link's accessible name is just the item name and the count is moved to a hidden element referenced by aria-describedby.
Mechanism (consistent across all of them):
- The visible count bubble gets
aria-hidden="true". - The count text lives in a visually-hidden
screen-reader-textelement with a uniqueid. - The link references that element with
aria-describedby.
Implementation notes / gotchas I ran into:
- In the admin menu the count text is part of the menu title string, but
aria-describedbyhas to land on the<a>, which is rendered generically inmenu-header.php. So the title carries a marker description span andmenu-header.phplifts itsidonto the link — this covers both top-level and submenu anchors. menu-header.phprepeats the top-level title in thewp-submenu-headelement, which duplicated the descriptionid. The PR strips the description span from that repeated copy so eachidstays unique.- The toolbar items are icon-only, so removing the count from the name would leave them unnamed — the PR adds an explicit hidden name (e.g. "Comments") in addition to the described-by count.
WP_Admin_Baronly renders a fixed allowlist of anchor attributes, soaria-describedbywas added to that allowlist.- Classes that JS live-updates (
comments-in-moderation-text,updates-available-text,pending-count,*-count) are preserved so dynamic count updates keep working.
Two things I'd like input on before polishing:
- Wording of the new hidden strings (e.g.
%s plugin updates available,%s critical issues) — happy to match whatever the a11y team prefers. - For the menu Updates/Themes/Plugins items, the visible number is refreshed by the AJAX update checker but the new described text is not (Comments and the toolbar items already update, since they keep their existing JS-target classes). Should the JS be extended to also update the description in this ticket, or is that better as a follow-up?
@afercia commented on PR #12850:
5 weeks ago
#5
I asked Copilot to review this PR. I see potential problems both in the PR _and_ in the Copilot review.
- I'm concerned about the regex approach. I doesn't seem solid to me and that's up to Claude, used for the initial implementation of this PR.
- Copilot completely ignored this potential problem.
- Instead, it repeatedly warned about the element referenced by aria-describedby being hidden from assistive technology by the means of aria-hidden. That is plain wrong. The content of elements referenced by aria-describedby is exposed in the accessibility tree even if the element is aria-hidden and even if it's totally hidden with display: none.
So it appears that, at least in this PR, usage of AI is not only misleading but also unnecessarily time consuming.
@sanket.parmar commented on PR #12850:
5 weeks ago
#6
Thanks @afercia — you're right on both points.
On the Copilot flags: agreed, they're incorrect. An element referenced by aria-describedby is included in the description computation even when it's aria-hidden (or display:none) — per the Accessible Name and Description Computation, a hidden node that's directly referenced isn't skipped. The aria-hidden on the description span is deliberate: it sits inside the link, so aria-hidden keeps its text out of the link's accessible *name* while aria-describedby still exposes it as the *description*. So there's nothing to action there.
On the regex: agreed, that's the fragile part. I've reworked menu-header.php so the description id is passed through explicitly (a count_description entry on the menu item) rather than parsed out of the title HTML — the association no longer depends on attribute order or a plugin's markup shape, and the submenu-head dedup regex is gone too.
Separately, @irozum flagged a real regression I've now fixed in the same push: with the visible bubble aria-hidden, the Updates/Themes/Plugins menu descriptions would go stale after an AJAX update — wp.updates.refreshCount() now keeps them in sync (and clears them at zero).
@afercia commented on PR #12850:
4 weeks ago
#7
Let's see if Copilot gets it right now. I'm curious.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
In the admin menu and toolbar, several items show a count in a circle (Updates, Comments, Themes, Plugins, Site Health). Today the count is exposed inconsistently in each item's accessible name — Updates/Themes/Plugins/Site Health leak the raw number (*"Updates 2"*), Comments folds descriptive text into the name (*"Comments 3 Comments in moderation"*), and the icon-only toolbar items use the count text as the whole name. The result: the accessible name doesn't match the visible label, so voice-control users can't activate the item by name, and announcements are inconsistent.
This standardizes them so each link's accessible name is just the item name (*"Updates"*, *"Comments"*, …) and the count is exposed as a description via
aria-describedby, per the approach in the ticket.How it works — for every counter: the visible bubble is
aria-hidden="true"; the count text lives in a visually-hiddenscreen-reader-textelement with a uniqueid; the link references it witharia-describedby.menu.php): each counter title carries a marker description span;menu-header.phpgenerically lifts itsidonto the link (top-level *and* submenu anchors) and strips the span from the repeatedwp-submenu-headso theidis never duplicated.admin-bar.php): Comments/Updates get an explicit hidden name plus a described-by count span;class-wp-admin-bar.phpallowsaria-describedbyas a nodemetaattribute.JS-updated classes (
comments-in-moderation-text,updates-available-text,pending-count,*-count) are preserved so live count updates keep working.How to test
aria-describedby→ a hiddenscreen-reader-textspan, the number span isaria-hidden="true", and each descriptionidappears exactly once in the DOM.Automated tests —
tests/phpunit/tests/admin/wpMenuOutput.php(new, describedby wiring + duplicate-id regression) and additions totests/phpunit/tests/adminbar.php(toolbar +metawhitelist).Open questions — exact wording of the new hidden strings; and the menu Updates/Themes/Plugins descriptions aren't yet refreshed by the AJAX count updater (Comments and toolbar are), so a follow-up would keep the described count from going stale.
## Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.8
Used for: Root cause analysis, locating the affected menu/toolbar rendering paths, initial patch and test implementation; final implementation, accessibility markup review, and correctness of the capability guards reviewed and edited by me.