Skip to content
4 changes: 3 additions & 1 deletion src/wp-admin/css/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -536,12 +536,14 @@ code {
vertical-align: top;
}

.widefat tbody th.check-column {
.widefat tbody th.check-column,
.widefat tbody td.check-column {
padding: 9px 0 22px;
}

.widefat thead td.check-column,
.widefat tbody th.check-column,
.widefat tbody td.check-column,
.updates-table tbody td.check-column,
.widefat tfoot td.check-column {
padding: 11px 0 0 3px;
Expand Down
3 changes: 2 additions & 1 deletion src/wp-admin/css/forms.css
Original file line number Diff line number Diff line change
Expand Up @@ -1967,7 +1967,8 @@ table.form-table td .updated p {
margin-left: 0;
}

.wp-list-table.privacy_requests tr:not(.inline-edit-row):not(.no-items) td.column-primary:not(.check-column) {
.wp-list-table.privacy_requests tr:not(.inline-edit-row):not(.no-items) td.column-primary:not(.check-column),
.wp-list-table.privacy_requests tr:not(.inline-edit-row):not(.no-items) th.column-primary:not(.check-column) {
display: table-cell;
}

Expand Down
37 changes: 26 additions & 11 deletions src/wp-admin/css/list-tables.css
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,13 @@
background-color: #fcf9e8;
}

#the-comment-list .unapproved th.check-column {
#the-comment-list .unapproved th.check-column,
#the-comment-list .unapproved td.check-column {
border-left: 4px solid #d63638;
}

#the-comment-list .unapproved th.check-column input {
#the-comment-list .unapproved th.check-column input,
#the-comment-list .unapproved td.check-column input {
margin-left: 4px;
}

Expand Down Expand Up @@ -1217,11 +1219,13 @@ ul.cat-checklist input[name="post_category[]"]:indeterminate::before {
------------------------------------------------------------------------------*/

.plugins tbody th.check-column,
.plugins tbody td.check-column,
.plugins tbody {
padding: 8px 0 0 2px;
}

.plugins tbody th.check-column input[type=checkbox] {
.plugins tbody th.check-column input[type=checkbox],
.plugins tbody td.check-column input[type=checkbox] {
margin-top: 4px;
}

Expand All @@ -1231,7 +1235,8 @@ ul.cat-checklist input[name="post_category[]"]:indeterminate::before {

.plugins thead td.check-column,
.plugins tfoot td.check-column,
.plugins .inactive th.check-column {
.plugins .inactive th.check-column,
.plugins .inactive td.check-column {
padding-left: 6px;
}

Expand Down Expand Up @@ -1321,6 +1326,7 @@ ul.cat-checklist input[name="post_category[]"]:indeterminate::before {
}

.plugins .active th.check-column,
.plugins .active td.check-column,
.plugin-update-tr.active td {
border-left: 4px solid var(--wp-admin-theme-color);
}
Expand Down Expand Up @@ -1406,7 +1412,8 @@ ul.cat-checklist input[name="post_category[]"]:indeterminate::before {
text-decoration: underline;
}

.plugins tr.paused th.check-column {
.plugins tr.paused th.check-column,
.plugins tr.paused td.check-column {
border-left: 4px solid #b32d2e;
}

Expand Down Expand Up @@ -1905,7 +1912,8 @@ div.action-links,
}

.wp-list-table th.column-primary ~ th,
.wp-list-table tr:not(.inline-edit-row):not(.no-items) td.column-primary ~ td:not(.check-column) {
.wp-list-table tr:not(.inline-edit-row):not(.no-items) td.column-primary ~ td:not(.check-column),
.wp-list-table tr:not(.inline-edit-row):not(.no-items) th.column-primary ~ td:not(.check-column) {
display: none;
}

Expand All @@ -1914,7 +1922,8 @@ div.action-links,
}

/* Checkboxes need to show */
.wp-list-table tr th.check-column {
.wp-list-table tr th.check-column,
.wp-list-table tr td.check-column {
display: table-cell;
}

Expand All @@ -1932,11 +1941,13 @@ div.action-links,
width: auto !important; /* needs to override some columns that are more specifically targeted */
}

.wp-list-table td.column-primary {
.wp-list-table td.column-primary,
.wp-list-table th.column-primary {
padding-right: 50px; /* space for toggle button */
}

.wp-list-table tr:not(.inline-edit-row):not(.no-items) td.column-primary ~ td:not(.check-column) {
.wp-list-table tr:not(.inline-edit-row):not(.no-items) td.column-primary ~ td:not(.check-column),
.wp-list-table tr:not(.inline-edit-row):not(.no-items) th.column-primary ~ td:not(.check-column) {
padding: 3px 8px 3px 35%;
}

Expand Down Expand Up @@ -2265,12 +2276,14 @@ div.action-links,
}

.plugins tr.active + tr.inactive th.check-column,
.plugins tr.active + tr.inactive td.check-column,
.plugins tr.active + tr.inactive td.column-description,
.plugins .plugin-update-tr:before {
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1);
}

.plugins tr.active + tr.inactive th.check-column,
.plugins tr.active + tr.inactive td.check-column,
.plugins tr.active + tr.inactive td {
border-top: none;
}
Expand Down Expand Up @@ -2305,13 +2318,15 @@ div.action-links,
line-height: 1.5;
}

.plugins tbody th.check-column {
.plugins tbody th.check-column,
.plugins tbody td.check-column {
padding: 8px 0 0 5px;
}

.plugins thead td.check-column,
.plugins tfoot td.check-column,
.plugins .inactive th.check-column {
.plugins .inactive th.check-column,
.plugins .inactive td.check-column {
padding-left: 9px;
}

Expand Down
53 changes: 43 additions & 10 deletions src/wp-admin/includes/class-wp-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -1767,6 +1767,26 @@ protected function column_default( $item, $column_name ) {}
*/
protected function column_cb( $item ) {}

/**
* Returns a clean, human-readable label for the primary column's row header.
*
* Used as the `aria-label` attribute value on the `<th scope="row">` element,
* giving screen readers a concise cell name instead of computing it from
* the full cell content (which may include row action links, excerpts, etc.).
*
* Subclasses should override this method to return the item's primary
* identifier (e.g. post title, plugin name, username). Return an empty string
* to omit the attribute.
*
* @since 6.9.0
*
* @param object|array $item The current item.
* @return string The aria-label value, or an empty string.
*/
protected function get_primary_column_aria_label( $item ) {
return '';
}

/**
* Generates the columns for a single row of the table.
*
Expand Down Expand Up @@ -1796,9 +1816,9 @@ protected function single_row_columns( $item ) {
$attributes = "class='$classes' $data";

if ( 'cb' === $column_name ) {
echo '<th scope="row" class="check-column">';
echo '<td class="check-column">';
echo $this->column_cb( $item );
echo '</th>';
echo '</td>';
} elseif ( method_exists( $this, '_column_' . $column_name ) ) {
echo call_user_func(
array( $this, '_column_' . $column_name ),
Expand All @@ -1807,16 +1827,29 @@ protected function single_row_columns( $item ) {
$data,
$primary
);
} elseif ( method_exists( $this, 'column_' . $column_name ) ) {
echo "<td $attributes>";
echo call_user_func( array( $this, 'column_' . $column_name ), $item );
echo $this->handle_row_actions( $item, $column_name, $primary );
echo '</td>';
} else {
echo "<td $attributes>";
echo $this->column_default( $item, $column_name );
$is_primary = ( $primary === $column_name );
$tag = $is_primary ? 'th' : 'td';
$scope = $is_primary ? ' scope="row"' : '';

$aria_label = '';
if ( $is_primary ) {
$label = $this->get_primary_column_aria_label( $item );
if ( '' !== $label ) {
$aria_label = ' aria-label="' . esc_attr( $label ) . '"';
}
}

echo "<$tag $attributes$scope$aria_label>";

if ( method_exists( $this, 'column_' . $column_name ) ) {
echo call_user_func( array( $this, 'column_' . $column_name ), $item );
} else {
echo $this->column_default( $item, $column_name );
}

echo $this->handle_row_actions( $item, $column_name, $primary );
echo '</td>';
echo "</$tag>";
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/wp-admin/includes/class-wp-ms-themes-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -940,11 +940,11 @@ public function single_row_columns( $item ) {

switch ( $column_name ) {
case 'cb':
echo '<th scope="row" class="check-column">';
echo '<td class="check-column">';

$this->column_cb( $item );

echo '</th>';
echo '</td>';
break;

case 'name':
Expand All @@ -966,11 +966,11 @@ public function single_row_columns( $item ) {
}
}

echo "<td class='theme-title column-primary{$extra_classes}'><strong>" . $item->display( 'Name' ) . $active_theme_label . '</strong>';
echo "<th scope='row' class='theme-title column-primary{$extra_classes}' aria-label='" . esc_attr( $item->display( 'Name' ) ) . "'><strong>" . $item->display( 'Name' ) . $active_theme_label . '</strong>';

$this->column_name( $item );

echo '</td>';
echo '</th>';
break;

case 'description':
Expand Down
6 changes: 3 additions & 3 deletions src/wp-admin/includes/class-wp-plugins-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -1233,12 +1233,12 @@ public function single_row( $item ) {

switch ( $column_name ) {
case 'cb':
echo "<th scope='row' class='check-column'>$checkbox</th>";
echo "<td class='check-column'>$checkbox</td>";
break;
case 'name':
echo "<td class='plugin-title column-primary'><strong>$plugin_name</strong>";
echo "<th scope='row' class='plugin-title column-primary' aria-label='" . esc_attr( $plugin_name ) . "'><strong>$plugin_name</strong>";
echo $this->row_actions( $actions, true );
echo '</td>';
echo '</th>';
break;
case 'description':
$classes = 'column-description desc';
Expand Down
22 changes: 20 additions & 2 deletions src/wp-admin/includes/class-wp-posts-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -1072,10 +1072,28 @@ public function column_cb( $item ) {
* @param string $primary
*/
protected function _column_title( $post, $classes, $data, $primary ) {
echo '<td class="' . $classes . ' page-title" ', $data, '>';
$aria_label = $this->get_primary_column_aria_label( $post );
$aria_attr = ( '' !== $aria_label ) ? ' aria-label="' . esc_attr( $aria_label ) . '"' : '';
echo '<th scope="row" class="' . $classes . ' page-title" ', $data, $aria_attr, '>';
echo $this->column_title( $post );
echo $this->handle_row_actions( $post, 'title', $primary );
echo '</td>';
echo '</th>';
}

/**
* Returns a clean label for the primary (title) column's row header `aria-label`.
*
* Provides screen readers with just the post title as the row header name,
* preventing them from computing the name from the full cell content
* (which includes row action links, post states, and possibly an excerpt).
*
* @since 6.9.0
*
* @param WP_Post $item The current post object.
* @return string The post title, or an empty string if unavailable.
*/
protected function get_primary_column_aria_label( $item ) {
return isset( $item->post_title ) ? $item->post_title : '';
}

/**
Expand Down
14 changes: 11 additions & 3 deletions src/wp-admin/includes/class-wp-users-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -563,9 +563,16 @@ public function single_row( $user_object, $style = '', $role = '', $numposts = 0
$attributes = "class='$classes' $data";

if ( 'cb' === $column_name ) {
$row .= "<th scope='row' class='check-column'>$checkbox</th>";
$row .= "<td class='check-column'>$checkbox</td>";
} else {
$row .= "<td $attributes>";
$is_primary = ( $primary === $column_name );
$tag = $is_primary ? 'th' : 'td';
$scope = $is_primary ? ' scope="row"' : '';
$aria_label = '';
if ( $is_primary ) {
$aria_label = ' aria-label="' . esc_attr( $user_object->user_login ) . '"';
}
$row .= "<$tag $attributes$scope$aria_label>";
switch ( $column_name ) {
case 'username':
$row .= "$avatar $edit";
Expand Down Expand Up @@ -628,7 +635,8 @@ public function single_row( $user_object, $style = '', $role = '', $numposts = 0
if ( $primary === $column_name ) {
$row .= $this->row_actions( $actions );
}
$row .= '</td>';
$tag = ( $primary === $column_name ) ? 'th' : 'td';
$row .= "</$tag>";
}
}
$row .= '</tr>';
Expand Down
Loading