Make WordPress Core

Opened 6 years ago

Last modified 3 weeks ago

#50486 accepted defect (bug)

Improve the admin notices accessibility

Reported by: afercia's profile afercia Owned by: joedolson's profile joedolson
Milestone: 7.1 Priority: normal
Severity: normal Version:
Component: Administration Keywords: needs-patch
Focuses: ui, accessibility Cc:

Description

Follow-up to #47656. See also #50442/

The admin notices accessibility has room for improvements.

Currently, all the admin notices are just text printed out on the admin pages. They have no special semantics. There are no mechanisms to inform users some important information is printed on the page other than visuals. There are no methods to navigate directly to admin notices nor established standards on their placement.

This way, users with accessibility needs will likely miss important information, as they may have no clue whether and where these messages are printed out.

Depending whether the admin notices are PHP-generated notices that appear after a page load or JavaScript-generated ones that are "injected" into the DOM, there are ways to improve their accessibility. At the very least, the following improvements should be considered, some of them were mentioned in #47656.

All notices

  • establish standards for the admin notices placement in the page: there should be one, well defined, area where notices are printed out (recommended: after the main H1)
  • establish standards for the admin notices content: see the discussion related to the "text prefix" on #47656 and #50442
  • the above item should apply to the whole WordPress UI, including the block editor
  • "inline" notices (the ones printed out in the middle of the page): make a decision whether to keep them or allow only notices at the top of the page
  • if "inline" notices are kept, explore ways to make them more accessible
  • worth considering admin notice should be visually prominent and appear within the initial viewport anyways
  • consider the introduction of a dedicated PHP class with methods to get / print admin notices and additional features, also keeping into consideration the WP Notify project and the recent call for feedback on the WP Notify v1 requirements document

PHP notices

  • prepend to the document title a short text to inform users there are notices and their number: the document title is the first thing screen readers announce after a page load
  • the notices should be printed out after the main H1 heading as that's the part of the content with higher chances to be navigated to
  • define the page area where notices are printed out with an ARIA role that's also a landmark role, e.g. complementary (an <aside> element) or other role that is a landmark
  • add a new "skip link" after the "Skip to main content" one to allow all keyboard users to jump to the notices area: this skip link should only be printed out when there are notices

JavaScript notices

  • all notices injected in the DOM should have an ARIA role alert or status depending on their "politeness" level, so that they're automatically announced by screen readers

Any additional suggestion and/or technique to improve the admin notices accessibility is very welcome.

Change History (46)

This ticket was mentioned in Slack in #accessibility by ryokuhi. View the logs.


5 years ago

This ticket was mentioned in Slack in #feature-notifications by ryokuhi. View the logs.


5 years ago

This ticket was mentioned in Slack in #feature-notifications by raaaahman. View the logs.


5 years ago

#4 @psykro
5 years ago

@afercia thanks for detailing these issues. To clarify what you have raised here, I have a couple of questions.

"inline" notices (the ones printed out in the middle of the page): make a decision whether to keep them or allow only notices at the top of the page

  1. Can you give me an example of this type of inline notice?

the notices should be printed out after the main H1 heading as that's the part of the content with higher chances to be navigated to

  1. Is this not currently the case? If I use the example notice from the official documentation:
<?php
function sample_admin_notice__success() {
        ?>
        <div class="notice notice-success is-dismissible">
                <p><?php _e( 'Done!', 'sample-text-domain' ); ?></p>
        </div>
        <?php
}
add_action( 'admin_notices', 'sample_admin_notice__success' );

it's rendered just below the main h1 of that admin page.

#5 follow-up: @ryokuhi
5 years ago

Hello @psykro,
You can find a screenshot in ticket:47656#comment:29: I think this is an example both of an inline notice and of a notice that is printed in the middle of the page and not right after the H1 element.

This ticket was mentioned in Slack in #accessibility by ryokuhi. View the logs.


5 years ago

This ticket was mentioned in Slack in #feature-notifications by raaaahman. View the logs.


5 years ago

#8 in reply to: ↑ description @raaaahman
5 years ago

Replying to afercia:

It would be worth coordinating the effort on that class with the WP Notify project indeed. If we manage to define a common interface for the 'get/set' methods, we may be able to change its implementation for the new dispatch mechanism when it become fully operational. We are going for a [Repository](https://github.com/WordPress/wp-notify/blob/develop/includes/persistence/interface-wp-notify-notification-repository.php), would this interface match your needs?

This ticket was mentioned in Slack in #feature-notifications by jon_bossenger. View the logs.


5 years ago

#10 in reply to: ↑ 5 @psykro
5 years ago

Replying to ryokuhi:

Hello @psykro,
You can find a screenshot in ticket:47656#comment:29: I think this is an example both of an inline notice and of a notice that is printed in the middle of the page and not right after the H1 element.

Thanks, @ryokuhi I see that this code is in the /wp-admin/plugin-editor.php file, line 283

https://github.com/WordPress/wordpress-develop/blob/6d574d7cabfaf6cc9797d2119ba1a10d9cd78fa3/src/wp-admin/plugin-editor.php#L283

The "problem" we have here is that it seems there are two ways that WordPress core renders admin notices from the PHP side.

  1. using the admin_notices action hook
  2. hardcoded inline in specific pages (like the plugin-editor.php)

So the first, and most important step will be to look for any instances where notices are hardcoded in this way, and replace them with admin_notices callback functions.

Once all admin notices are registered in the same way, we can apply rules to all admin notices, and be sure that they will all receive the same updates/changes.

Please let me know if you have any questions here.

Last edited 5 years ago by psykro (previous) (diff)

This ticket was mentioned in Slack in #accessibility by alexstine. View the logs.


4 years ago

#12 @alexstine
4 years ago

  • Keywords needs-patch added

#14 @joedolson
3 years ago

  • Owner set to joedolson
  • Status changed from new to accepted

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


3 years ago

#16 @joedolson
3 years ago

  • Milestone changed from Awaiting Review to 6.3

This ticket was mentioned in Slack in #core by oglekler. View the logs.


3 years ago

#18 @oglekler
3 years ago

  • Keywords dev-feedback added
  • Milestone changed from 6.3 to 6.4

This ticket was discussed in the recent bug scrub.

It looks like this cannot be ready to the Beta 1 and due to the existing project Feature notifications in needs to be revised, more info in #comment:8 but for now I am moving this ticket to 6.4

Additional props @mrinal

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


3 years ago

This ticket was mentioned in Slack in #core by oglekler. View the logs.


3 years ago

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


3 years ago

This ticket was mentioned in Slack in #core by marybaum. View the logs.


3 years ago

#23 @marybaum
3 years ago

Thanks to a note @joemcgill surfaced from @joedolson, this can move forward. That note is at https://wordpress.slack.com/archives/C02RP4X03/p1692376124664849

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


3 years ago

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


3 years ago

#26 @joedolson
3 years ago

  • Milestone changed from 6.4 to 6.5

This will not be ready for 6.4, but following the work done in #57791 during 6.4, I think it's plausible for 6.5.

#27 @joedolson
3 years ago

Also pinging @costdev on this ticket, as it's another thing related to #57791 to look at.

#28 @swissspidy
2 years ago

@joedolson Is this on your radar still for 6.5? Looks like a punt candidate given the lack of activity.

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


2 years ago

#30 @joedolson
2 years ago

  • Milestone changed from 6.5 to 6.6

Moving to 6.6; this will need more time.

This ticket was mentioned in Slack in #core by oglekler. View the logs.


23 months ago

This ticket was mentioned in Slack in #accessibility by rcreators. View the logs.


23 months ago

#33 @sabernhardt
23 months ago

  • Milestone changed from 6.6 to Future Release

I'm moving this to Future Release, but please add a numbered milestone as soon as the ticket has more activity.

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


18 months ago

#35 @joedolson
18 months ago

  • Milestone changed from Future Release to 6.8

This ticket was mentioned in Slack in #core by audrasjb. View the logs.


14 months ago

#37 @joedolson
14 months ago

  • Milestone changed from 6.8 to 6.9

Moving this to 6.9. Piece by piece, admin notices are getting improved, but the task this ticket represents needs a broader assessment.

#38 @davidbaumwald
7 months ago

  • Milestone changed from 6.9 to Future Release

It seems this ticket hasn't seen any momentum in a few years, just being punted to the next milestone several consecutive milestones.

Given that, moving this to Future Release. This can be pulled into a specific milestone if someone wishes to assume ownership during that cycle.

If any committer or maintainer feels this can be managed before 6.9's RC phase, feel free to pull i back into the milestone.

#39 @joedolson
6 months ago

  • Milestone changed from Future Release to 7.0

This ticket was mentioned in Slack in #core by audrasjb. View the logs.


3 months ago

#41 @joedolson
8 weeks ago

  • Milestone changed from 7.0 to Future Release

Bumping to future release. There's definitely things to be changed here, but there are simply a lot of notices.

One possible plan is to set up an observer script that can get all notices on the page and add a toggle to the adminbar that can provide access to them or summarize them. This would do a lot to increase the availability of notices, without requiring any significant changes to how individual notices are created or displayed.

#42 @sanket.parmar
3 weeks ago

Hello everyone,

I've reviewed this ticket and all comments, including the related work in #47656 and #57791. Given that this has been bumped across several milestones, I'd like to propose breaking this into smaller, independently mergeable pieces rather than tackling it all at once.

Proposed incremental plan

Step 1 — Audit and migrate hardcoded inline notices (PHP)

As noted in comment:10, some notices are hardcoded mid-page (e.g. wp-admin/plugin-editor.php) rather than hooked to admin_notices. These will never benefit from any landmark or skip-link improvements unless they're pulled into the standard hook first.

  • Audit all wp-admin/*.php files for inline <div class="notice..."> output that is not via the admin_notices action
  • Refactor those to use the hook, or at minimum ensure they render after the main <h1>

Why this is ordered first: This is a prerequisite for Step 2 only. If we add the landmark wrapper before fixing hardcoded notices, those notices will still render outside the landmark — giving false confidence that accessibility is fixed when it isn't for those edge-case admin pages. That said, Step 1 may turn out to be minor or a non-issue depending on what the audit finds.

Step 2 — Wrap the notices region in a landmark + add a skip link (PHP)

In wp-admin/admin-header.php, the admin_notices / all_admin_notices actions can be wrapped in a labelled landmark:

<div id="wp-admin-notices" role="complementary" aria-label="<?php esc_attr_e( 'Admin notices', 'default' ); ?>">
    <?php do_action( 'all_admin_notices' ); ?>
    <?php do_action( 'admin_notices' ); ?>
</div>

And a conditional skip link added alongside the existing "Skip to main content" one:

<?php if ( /* notices are present */ ) : ?>
<a class="skip-link screen-reader-text" href="#wp-admin-notices">
    <?php esc_html_e( 'Skip to notices', 'default' ); ?>
</a>
<?php endif; ?>

Who this helps: Keyboard and screen reader users who currently have no way to navigate directly to the notices region.

Step 3 — Prepend notice count to <title> when notices exist (PHP)

Screen readers announce the page <title> immediately after load. If there are notices, a short prefix helps:

/* translators: %d: number of admin notices */
$title_prefix = sprintf( _n( '[%d notice]', '[%d notices]', $notice_count, 'default' ), $notice_count );
$admin_title  = $title_prefix . ' ' . $admin_title;

Why this is independent: This doesn't depend on the landmark, the skip link, or any JS work. It could be done first, last, or in parallel with any other step. Low implementation risk, high screen reader impact.

Step 4 — Ensure JS-injected notices carry ARIA live region roles

All notices inserted into the DOM via JavaScript should carry either role="alert" (for errors — assertive announcement) or role="status" (for success/info — polite announcement). This is the standard WCAG 2.1 live region pattern.

This can be enforced in the wp-admin/js/ notice-handling scripts (e.g. common.js dismissible notice logic) and in the @wordpress/components Notice component on the Gutenberg side.

Why this is independent: This is entirely separate from the PHP-side work. It touches different files and could be tackled by a different contributor — or the Gutenberg team — simultaneously and without waiting for Steps 1–3.

Step 5 (complementary) — JS MutationObserver + admin bar summary

As suggested in comment:41 by @joedolson, a MutationObserver script could:

  • Watch the #wp-admin-notices region for new notices
  • Display a count badge in the admin bar
  • Provide a toggleable panel listing all current notices

This is a non-breaking enhancement that retroactively improves third-party plugin notices without requiring any changes to how those plugins register their notices. It doesn't depend on any of the prior steps, though its value is higher once Steps 1–2 have normalised where notices render.


Do we need all of these?

No. Each step addresses a different user group and failure mode:

Step Who benefits Standalone value?
1 — Audit inline notices Everyone Low alone; only needed to make Step 2 complete
2 — Landmark + skip link Keyboard & screen reader users High
3 — Title prefix Screen reader users High, minimal effort
4 — ARIA live regions (JS) Screen reader users (dynamic notices) High, fully independent
5 — Admin bar observer All users Medium, nice-to-have

The minimum viable improvement is Steps 2 + 3. Together they fix the two most impactful screen reader problems — landmark navigation and page title announcement — with purely PHP changes and minimal regression risk. That alone would be meaningful, shippable progress for this ticket.

Step 4 is the most important for the block editor side and can be handled independently, potentially by the Gutenberg team.

Steps 1 and 5 can be decided based on what the audit reveals and available bandwidth.


I'm happy to put together a patch for Steps 2 and 3 as a starting point. Does this approach align with @joedolson's current thinking?

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


3 weeks ago

#44 @joedolson
3 weeks ago

  • Milestone changed from Future Release to 7.1

Thanks for all your thoughts @sanketparmar!

There are a few details that I would probably change; in particular, I'd probably want to use an aside element instead of a div with role="complementary". But these are minor details that can be worked out in development.

I have a partial PR already for some of the outstanding admin notices to adjust. It's woefully out of date, but I could refresh it and complete it. https://github.com/WordPress/wordpress-develop/pull/5225.

The inline notices are definitely separate. I'm wondering if what we actually want to do is turn those into two separate classes of notices. There are sound reasons for using inline notices - in particular, for dynamic actions. We can consider that all inline notices should be delivered dynamically & should trigger a spoken notification, but not be included in the same class as notices that are loaded during a page load. They'll each need individual consideration, however, as there are a lot of variations in how these are used in core...

With your work on detailing a process and willingness to help out, I'm going to go ahead and milestone this for 7.1. It would be a great set of changes to get done.

#45 @joedolson
3 weeks ago

  • Keywords dev-feedback removed

#46 @sanket.parmar
3 weeks ago

Thank you for the feedback and for milestoning this for 7.1, @joedolson — great to have a concrete target!

On <aside> vs <div role="complementary">

Completely agree — native HTML5 semantics are always preferable over ARIA role overrides. An <aside> with an aria-label gives the same landmark without depending on ARIA alone, and is more robust across different AT implementations. Happy to use that in the patch.

On the existing PR

I had a look at PR #5225 — the branch was deleted when it was closed in March 2024, so there's nothing to rebase from directly. That said, the PR diff is still visible and gives a clear picture of the direction and scope of the changes. My suggestion would be to start a fresh PR using the closed PR as a reference for which notices were covered and how they were approached, rather than trying to recover the old code.

  • Would you be taking the lead on that, or would it help if I put together a fresh PR based on what was in that old PR?
  • Are there any notices from that PR that you'd want to handle differently now, given the two years since it was closed?

Happy to take on whichever part is most useful.

On inline / dynamic notices as a separate class

This distinction makes a lot of sense. The way I read it, there would be two separate categories going forward:

  • Page-load notices — PHP-generated, rendered once after page load, covered by the landmark + skip link + title prefix approach
  • Dynamic/inline notices — JS-injected or triggered by user actions mid-page, each needing individual assessment, and delivering a spoken notification via role="alert" or role="status" depending on urgency

The second category being treated individually is the right call given how varied the usages are across core. It also means the two tracks can progress independently without blocking each other.

Would it make sense to open a child ticket specifically for the dynamic notices audit, so the two tracks can be tracked and reviewed separately? That would help keep this ticket focused on the page-load side and avoid it ballooning in scope again.

Note: See TracTickets for help on using tickets.