Make WordPress Core

Opened 7 weeks ago

Closed 9 days ago

#65707 closed defect (bug) (fixed)

The Application Passwords table misses a th row header when first created via AJAX

Reported by: afercia Owned by: afercia
Priority: normal Milestone: 7.2
Component: Administration Version:
Severity: normal Keywords: has-patch has-unit-tests commit
Cc: Focuses: accessibility

Description

Splitting this out from #32892.

Tables that extend WP_List_Table are meant to use a <th> element as row header so that when navigating the table vertically with a screen reader, the row header can be referenced and announced to provide context to users.

In [62838] we also added an aria-label attribute to the <th> row header to provide a cleaner accessible name.

However, the table generated for the Application Passwords entirely misses a <th> element when first created via AJAX. The <th> is only rendered after refreshing the page.

The <th> element should be rendered also when first created via AJAX.

To reproduce:

  • Go to Users > User Profile, or edit an existing user.
  • Scroll down the page to the 'Application Passwords' section.
  • Create a new app password.
  • Inspect the DOM via your browser dev tools and observe all the cell of the table row are <td> elements.
  • Refresh the page.
  • Inspect the DOM again and observe that now the cell with the app name is a <th> element, with a scope="row" attribute. This is the expected markup.

Change History (7)

This ticket was mentioned in PR #12690 on WordPress/wordpress-develop by @khokansardar.


7 weeks ago
#1

  • Keywords has-patch has-unit-tests added

Tables extending WP_List_Table render the primary column as a <th scope="row"> element so screen readers can announce it as context when navigating the table vertically. The Application Passwords table did this for server-rendered rows but not for the JavaScript row template used when a password is created via AJAX, so the row header was missing until reload.

What the problem was:

  • On page load, the app-name cell is a <th scope="row"> (correct).
  • After creating a new application password via AJAX, every cell in the new row was a <td> — the <th> row header was missing.
  • The header only appeared after refreshing the page.

What the fix does:

  • Renders the primary column of print_js_template_row() as a <th scope="row"> element, mirroring WP_List_Table::single_row_columns().

Approach and why:

  • The AJAX row is produced verbatim from this PHP template, so the parity fix belongs here. single_row_columns() uses the same $tag/$scope logic; reusing it keeps the two rendering paths identical. No aria-label is added because the table does not override get_primary_column_aria_label(), so the server path emits none either — adding one would introduce a new mismatch.

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

## Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.8
Used for: Ticket analysis, tests implementation, and writing PR description. All changes were reviewed and validated by me.

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


5 weeks ago

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


3 weeks ago

#4 @joedolson
3 weeks ago

  • Milestone Awaiting Review7.2

@khokansardar commented on PR #12690:


10 days ago
#5

LGTM thank you. I only left two very minor comments.

Thanks @afercia! Both addressed in 9e86fa2 — added a description to the property docblock, and dropped the <td> assertion since the <th scope="row"> one already covers it.

#6 @afercia
9 days ago

  • Keywords commit added
  • Owner set to afercia
  • Status newassigned

#7 @afercia
9 days ago

  • Resolutionfixed
  • Status assignedclosed

In 63398:

Administration: Render a table row header in the Application Passwords AJAX row template.

Tables extending WP_List_Table use a <th scope="row"> element as the row
header for the primary column. WP_List_Table::single_row_columns() renders
this for server-generated rows, but the JavaScript template used when a new
application password is created via AJAX rendered every cell as a <td> element,
leaving the row header missing until the page was reloaded.

Renders the primary column of the JavaScript row template as a
<th scope="row"> element, mirroring the server-generated markup.

Props khokansardar, mukesh27, afercia.
See #32892.
Fixes #65707.

Note: See TracTickets for help on using tickets.