diff --git a/src/wp-admin/css/common.css b/src/wp-admin/css/common.css index 53933f0ac28a2..a6d7606015802 100644 --- a/src/wp-admin/css/common.css +++ b/src/wp-admin/css/common.css @@ -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; diff --git a/src/wp-admin/css/forms.css b/src/wp-admin/css/forms.css index c17d038c5d2c6..dd19e1ba8070a 100644 --- a/src/wp-admin/css/forms.css +++ b/src/wp-admin/css/forms.css @@ -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; } diff --git a/src/wp-admin/css/list-tables.css b/src/wp-admin/css/list-tables.css index 4eaa682127cc0..df6236e23d8d6 100644 --- a/src/wp-admin/css/list-tables.css +++ b/src/wp-admin/css/list-tables.css @@ -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; } @@ -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; } @@ -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; } @@ -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); } @@ -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; } @@ -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; } @@ -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; } @@ -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%; } @@ -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; } @@ -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; } diff --git a/src/wp-admin/includes/class-wp-list-table.php b/src/wp-admin/includes/class-wp-list-table.php index c243eece7035d..cfc0346bc4c2a 100644 --- a/src/wp-admin/includes/class-wp-list-table.php +++ b/src/wp-admin/includes/class-wp-list-table.php @@ -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 `` 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. * @@ -1796,9 +1816,9 @@ protected function single_row_columns( $item ) { $attributes = "class='$classes' $data"; if ( 'cb' === $column_name ) { - echo ''; + echo ''; echo $this->column_cb( $item ); - echo ''; + echo ''; } elseif ( method_exists( $this, '_column_' . $column_name ) ) { echo call_user_func( array( $this, '_column_' . $column_name ), @@ -1807,16 +1827,29 @@ protected function single_row_columns( $item ) { $data, $primary ); - } elseif ( method_exists( $this, 'column_' . $column_name ) ) { - echo ""; - echo call_user_func( array( $this, 'column_' . $column_name ), $item ); - echo $this->handle_row_actions( $item, $column_name, $primary ); - echo ''; } else { - echo ""; - 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 ''; + echo ""; } } } diff --git a/src/wp-admin/includes/class-wp-ms-themes-list-table.php b/src/wp-admin/includes/class-wp-ms-themes-list-table.php index a0fca2fd60fe4..81c35414d9053 100644 --- a/src/wp-admin/includes/class-wp-ms-themes-list-table.php +++ b/src/wp-admin/includes/class-wp-ms-themes-list-table.php @@ -940,11 +940,11 @@ public function single_row_columns( $item ) { switch ( $column_name ) { case 'cb': - echo ''; + echo ''; $this->column_cb( $item ); - echo ''; + echo ''; break; case 'name': @@ -966,11 +966,11 @@ public function single_row_columns( $item ) { } } - echo "" . $item->display( 'Name' ) . $active_theme_label . ''; + echo "" . $item->display( 'Name' ) . $active_theme_label . ''; $this->column_name( $item ); - echo ''; + echo ''; break; case 'description': diff --git a/src/wp-admin/includes/class-wp-plugins-list-table.php b/src/wp-admin/includes/class-wp-plugins-list-table.php index 08b2e982e702f..d8945e103064e 100644 --- a/src/wp-admin/includes/class-wp-plugins-list-table.php +++ b/src/wp-admin/includes/class-wp-plugins-list-table.php @@ -1233,12 +1233,12 @@ public function single_row( $item ) { switch ( $column_name ) { case 'cb': - echo "$checkbox"; + echo "$checkbox"; break; case 'name': - echo "$plugin_name"; + echo "$plugin_name"; echo $this->row_actions( $actions, true ); - echo ''; + echo ''; break; case 'description': $classes = 'column-description desc'; diff --git a/src/wp-admin/includes/class-wp-posts-list-table.php b/src/wp-admin/includes/class-wp-posts-list-table.php index 18c76169eb81c..f8bc624b52ffd 100644 --- a/src/wp-admin/includes/class-wp-posts-list-table.php +++ b/src/wp-admin/includes/class-wp-posts-list-table.php @@ -1072,10 +1072,28 @@ public function column_cb( $item ) { * @param string $primary */ protected function _column_title( $post, $classes, $data, $primary ) { - echo ''; + $aria_label = $this->get_primary_column_aria_label( $post ); + $aria_attr = ( '' !== $aria_label ) ? ' aria-label="' . esc_attr( $aria_label ) . '"' : ''; + echo ''; echo $this->column_title( $post ); echo $this->handle_row_actions( $post, 'title', $primary ); - echo ''; + echo ''; + } + + /** + * 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 : ''; } /** diff --git a/src/wp-admin/includes/class-wp-users-list-table.php b/src/wp-admin/includes/class-wp-users-list-table.php index 9a8709b438e05..dd54b200bafaf 100644 --- a/src/wp-admin/includes/class-wp-users-list-table.php +++ b/src/wp-admin/includes/class-wp-users-list-table.php @@ -563,9 +563,16 @@ public function single_row( $user_object, $style = '', $role = '', $numposts = 0 $attributes = "class='$classes' $data"; if ( 'cb' === $column_name ) { - $row .= "$checkbox"; + $row .= "$checkbox"; } else { - $row .= ""; + $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"; @@ -628,7 +635,8 @@ public function single_row( $user_object, $style = '', $role = '', $numposts = 0 if ( $primary === $column_name ) { $row .= $this->row_actions( $actions ); } - $row .= ''; + $tag = ( $primary === $column_name ) ? 'th' : 'td'; + $row .= ""; } } $row .= '';