Skip to content

32892 Add more aria labels to list tables th row headers#12683

Open
afercia wants to merge 8 commits into
WordPress:trunkfrom
afercia:32892-more-aria-labels-list-tables-th
Open

32892 Add more aria labels to list tables th row headers#12683
afercia wants to merge 8 commits into
WordPress:trunkfrom
afercia:32892-more-aria-labels-list-tables-th

Conversation

@afercia

@afercia afercia commented Jul 24, 2026

Copy link
Copy Markdown
Member

Trac ticket: https://core.trac.wordpress.org/ticket/32892

Adds aria-label attributes to more list table row headers, to provide cleaner names for screen reader users.
Corrects @since notation after changeset 62838.

Use of AI Tools

None


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

Copilot AI review requested due to automatic review settings July 24, 2026 14:40
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props afercia, mukesh27, joedolson.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends WP_List_Table accessibility support by adding per-row aria-label values for primary-column row headers across several admin list tables, and updates @since annotations to match current core versioning.

Changes:

  • Adds get_primary_column_aria_label() overrides to multiple list tables so row headers expose a cleaner accessible name (e.g., term name, email, username, attachment title).
  • Updates @since tags for the get_primary_column_aria_label() API to 7.1.0 in core list table code and in the Posts list table implementation.
  • Includes a small whitespace alignment fix in class-wp-posts-list-table.php.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/wp-admin/includes/class-wp-terms-list-table.php Adds a primary-column aria-label provider using the term name.
src/wp-admin/includes/class-wp-privacy-data-removal-requests-list-table.php Adds a primary-column aria-label provider using requester email.
src/wp-admin/includes/class-wp-privacy-data-export-requests-list-table.php Adds a primary-column aria-label provider using requester email.
src/wp-admin/includes/class-wp-posts-list-table.php Updates @since for the posts implementation and fixes minor whitespace.
src/wp-admin/includes/class-wp-ms-users-list-table.php Adds a primary-column aria-label provider using user_login.
src/wp-admin/includes/class-wp-ms-sites-list-table.php Adds a primary-column aria-label provider using the site title via blog switching.
src/wp-admin/includes/class-wp-media-list-table.php Adds a primary-column aria-label provider using the attachment title.
src/wp-admin/includes/class-wp-list-table.php Updates @since for the base get_primary_column_aria_label() API.
src/wp-admin/includes/class-wp-links-list-table.php Adds a primary-column aria-label provider using the link name.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +899 to +905
protected function get_primary_column_aria_label( $blog ) {
switch_to_blog( $blog['blog_id'] );
$site_title = get_option( 'blogname' );
restore_current_blog();

return $site_title;
}
* to omit the attribute.
*
* @since 6.9.0
* @since 7.1.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change needed?

@mukeshpanchal27 mukeshpanchal27 Jul 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new code in WP 7.1. I guess the notation was copied and pasted from other methods introduced in 6.9.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that was just overlooked.

Copilot AI review requested due to automatic review settings July 24, 2026 19:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/wp-admin/includes/class-wp-ms-sites-list-table.php:901

  • get_primary_column_aria_label() currently fetches the site title via get_blog_option( ..., 'blogname' ), which (a) can add an extra per-row blog option lookup, and (b) does not match what the primary blogname column actually displays (the site URL/domain+path). For the row header aria-label, returning the same URL string used in column_blogname() avoids the extra lookup and keeps the accessible name aligned with the visible primary identifier.
	protected function get_primary_column_aria_label( $blog ) {
		return get_blog_option( $blog['blog_id'], 'blogname' );
	}

@joedolson joedolson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of these names can return HTML, and should be cleaned up before use.

* @return string The attachment title.
*/
protected function get_primary_column_aria_label( $post ) {
return _draft_or_post_title( $post );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_draft_or_post_title() can return escaped HTML, either inserted from the the_title filter or directly written into the post_title field. So this probably needs to reverse the escaping then strip HTML before use.

* @return string The Site title.
*/
protected function get_primary_column_aria_label( $blog ) {
return get_blog_option( $blog['blog_id'], 'blogname' );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The blogname can also contain HTML, but would not be expected to be escaped. Can probably just use wp_strip_all_tags() here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, as far as I can tell the value get stored in the database already escaped. So that it may be something like My <div>books. We need to first decode the HTML entities and then strip the HTML. I observed there are a few cases of this pattern in the Customizer and in the REST API.

esc_html( sprintf( __( 'Child of %s' ), wp_strip_all_tags( $parent_title ) ) )
);
$hierarchy_nolink = sprintf(
$hierarchy_nolink = sprintf(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this as it need two spaces

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That only applies to consecutive lines, which is not the case here.

Copilot AI review requested due to automatic review settings July 26, 2026 16:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/wp-admin/includes/class-wp-ms-sites-list-table.php:903

  • get_blog_option() switches blogs internally (via switch_to_blog()), so calling it here introduces an extra blog context switch per row. It also makes the row header’s accessible name differ from the visible primary-column content (URL). Consider using the already-available site URL (domain + path) as the clean aria-label instead, and update the docblock accordingly.
	protected function get_primary_column_aria_label( $blog ) {
		$blog_name = html_entity_decode( get_blog_option( $blog['blog_id'], 'blogname' ), ENT_QUOTES, get_bloginfo( 'charset' ) );
		$blog_name = wp_strip_all_tags( $blog_name );

		return $blog_name;

Comment on lines +947 to +956
protected function get_primary_column_aria_label( $post ) {
$attachment_title = $post->post_title ?? '';

if ( ! empty( $attachment_title ) ) {
// The title may contain HTML. The printed aria-label uses esc_attr() later.
$attachment_title = wp_strip_all_tags( $attachment_title );
}

return $attachment_title;
}
* @return string The User login.
*/
protected function get_primary_column_aria_label( $user ) {
return $user->user_login;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe usernames cannot be created with illegal characters so they can't contain HTML.

* @return string The term name.
*/
protected function get_primary_column_aria_label( $tag ) {
return $tag->name;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe term names cannot be created with illegal characters so they can't contain HTM. It is stripped out during the term name creation.

Copilot AI review requested due to automatic review settings July 26, 2026 17:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/wp-admin/includes/class-wp-ms-sites-list-table.php:903

  • get_primary_column_aria_label() uses get_blog_option( $blog['blog_id'], 'blogname' ), which will trigger an options lookup per row in the Sites list table. On large multisite networks this can introduce an N+1 performance regression during table rendering. The primary column is labeled “URL” and the URL is already available in the $blog array, so the aria-label can be derived without extra queries.
	protected function get_primary_column_aria_label( $blog ) {
		$blog_name = html_entity_decode( get_blog_option( $blog['blog_id'], 'blogname' ), ENT_QUOTES, get_bloginfo( 'charset' ) );
		$blog_name = wp_strip_all_tags( $blog_name );

		return $blog_name;

src/wp-admin/includes/class-wp-media-list-table.php:955

  • This returns an empty string for untitled attachments, which causes the aria-label to be omitted and screen readers to fall back to the full cell content (including row actions). It also returns raw $post->post_title without the same “(no title)” fallback used elsewhere in WP admin list tables.
	protected function get_primary_column_aria_label( $post ) {
		$attachment_title = $post->post_title ?? '';

		if ( ! empty( $attachment_title ) ) {
			// The title may contain HTML. The printed aria-label uses esc_attr() later.
			$attachment_title = wp_strip_all_tags( $attachment_title );
		}

		return $attachment_title;

Copilot AI review requested due to automatic review settings July 26, 2026 18:06
@afercia
afercia force-pushed the 32892-more-aria-labels-list-tables-th branch from e18e584 to 6048ca5 Compare July 26, 2026 18:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/wp-admin/includes/class-wp-ms-sites-list-table.php:903

  • get_blog_option( $blog['blog_id'], 'blogname' ) triggers switch_to_blog() for every rendered row, which can noticeably slow down the Sites list table (especially for large networks). Since a concise label is the goal, consider using the already-available URL (domain+path) for the aria-label to avoid per-row blog context switches during rendering.
		$blog_name = html_entity_decode( get_blog_option( $blog['blog_id'], 'blogname' ), ENT_QUOTES, get_bloginfo( 'charset' ) );
		$blog_name = wp_strip_all_tags( $blog_name );

		return $blog_name;

Copilot AI review requested due to automatic review settings July 26, 2026 18:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment on lines +899 to +904
protected function get_primary_column_aria_label( $blog ) {
$blog_name = html_entity_decode( get_blog_option( $blog['blog_id'], 'blogname' ), ENT_QUOTES, get_bloginfo( 'charset' ) );
$blog_name = wp_strip_all_tags( $blog_name );

return $blog_name;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. To my understanding, the blog name is required when creating a site (it's the blog title field) but then it can be manually emptied after the site creation, thus leaving a blog without a blogname. I think this surfaces a bug that should be addressed separately,

Copilot AI review requested due to automatic review settings July 26, 2026 20:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/wp-admin/includes/class-wp-ms-sites-list-table.php:903

  • This uses get_blog_option( $blog['blog_id'], 'blogname' ), which calls switch_to_blog() internally (see wp-includes/ms-blogs.php:get_blog_option()), adding a blog switch for every row. In non-list mode, column_blogname() already switches blogs, so this doubles the switching work per row. Consider using the already-available URL (domain + path) as the aria-label to avoid the extra switching overhead.
	protected function get_primary_column_aria_label( $blog ) {
		$blog_name = html_entity_decode( get_blog_option( $blog['blog_id'], 'blogname' ), ENT_QUOTES, get_bloginfo( 'charset' ) );
		$blog_name = wp_strip_all_tags( $blog_name );

		// Fall back to the blog URL and path if the blog name is empty.

Comment on lines +766 to +768
protected function get_primary_column_aria_label( $tag ) {
return $tag->name;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, this is not needed because HTML is stripped out when creating a term name.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/wp-admin/includes/class-wp-posts-list-table.php:1136

  • In get_primary_column_aria_label(), the fallback string uses a new translatable __( 'no title' ) that is inconsistent with core’s existing untitled-post string __( '(no title)' ) (see _draft_or_post_title() in wp-admin/includes/template.php). Reusing the existing string avoids duplicate translations and keeps UI/ARIA wording consistent.
	 * @since 7.1.0
	 *
	 * @param WP_Post $item The current post object.
	 * @return string The post title, or 'no title' if no title.
	 */

* @since 7.1.0
*
* @param WP_User $user The current WP_User object.
* @return string The User login.
Comment on lines +888 to +898
/**
* Returns a clean label for the primary (URL) column's row header `aria-label`.
*
* Provides screen readers with just the Site Title as the row header name,
* preventing them from computing the name from the full cell content.
*
* @since 7.1.0
*
* @param array $blog The current site properties array.
* @return string The Site title.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants