On the Inner Circle, I was answering a question about getting the number of pages when a sitemap wasn’t available, and I quickly had ChatGPT throw this together. The person was looking for a tool to scope a project, so they would not have access to the dashboard, but it turned out to be a nice snippet.

<?php
/**
 * Dashboard widget: Post type counts by status (WCAG-friendly + centered numbers)
 * Drop into WPCodeBox as a PHP snippet.
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

add_action( 'wp_dashboard_setup', function () {
	if ( ! current_user_can( 'edit_posts' ) ) {
		return;
	}

	wp_add_dashboard_widget(
		'fw_post_type_status_counts',
		__( 'Content Totals by Status', 'fw' ),
		'fw_render_post_type_status_counts_widget'
	);
} );

function fw_render_post_type_status_counts_widget() {
	$post_types = get_post_types(
		[
			'public'  => true,
			'show_ui' => true,
		],
		'objects'
	);

	if ( empty( $post_types ) ) {
		echo '<p>' . esc_html__( 'No post types found.', 'fw' ) . '</p>';
		return;
	}

	// Statuses to display (adjust as needed).
	$statuses = [
		'publish' => __( 'Published', 'fw' ),
		'draft'   => __( 'Drafts', 'fw' ),
		'pending' => __( 'Pending', 'fw' ),
		'future'  => __( 'Scheduled', 'fw' ),
		'private' => __( 'Private', 'fw' ),
		'trash'   => __( 'Trash', 'fw' ),
	];

	$widget_id   = 'fw-pt-status-widget';
	$caption_id  = 'fw-pt-status-caption';
	$summary_id  = 'fw-pt-status-summary';
	$statuses_id = 'fw-pt-status-statuses';

	// Inline styles kept minimal; uses WP admin colors by default.
	echo '<style>
		#' . esc_attr( $widget_id ) . ' table{width:100%;border-collapse:collapse}
		#' . esc_attr( $widget_id ) . ' caption{padding:8px 0;text-align:left}
		#' . esc_attr( $widget_id ) . ' th,#' . esc_attr( $widget_id ) . ' td{padding:8px;border-bottom:1px solid #dcdcde;vertical-align:middle}
		#' . esc_attr( $widget_id ) . ' th{font-weight:600;text-align:left}
		#' . esc_attr( $widget_id ) . ' td.num,#' . esc_attr( $widget_id ) . ' th.num{text-align:center;white-space:nowrap}
		#' . esc_attr( $widget_id ) . ' .pt-slug{opacity:.8;font-size:12px;margin-top:2px}
		#' . esc_attr( $widget_id ) . ' a{color:inherit}
		#' . esc_attr( $widget_id ) . ' a:focus{outline:2px solid currentColor;outline-offset:2px}
	</style>';

	echo '<div id="' . esc_attr( $widget_id ) . '">';

	// Extra context for screen readers (and visible users) without being noisy.
	echo '<p id="' . esc_attr( $summary_id ) . '" class="description">';
	echo esc_html__( 'Counts are grouped by post type and status. Select a number to open the filtered list in the admin.', 'fw' );
	echo '</p>';

	// Legend list for abbreviations (if any) — here it helps SR users understand column meaning,
	// and gives a clear mapping of which statuses are present.
	echo '<div id="' . esc_attr( $statuses_id ) . '" class="description" style="margin:0 0 10px 0;">';
	echo '<strong>' . esc_html__( 'Statuses:', 'fw' ) . '</strong> ';
	echo esc_html( implode( ', ', array_values( $statuses ) ) );
	echo '</div>';

	echo '<table aria-describedby="' . esc_attr( $summary_id ) . ' ' . esc_attr( $statuses_id ) . '">';

	// Caption provides table purpose (WCAG-friendly).
	echo '<caption id="' . esc_attr( $caption_id ) . '">';
	echo esc_html__( 'Post type totals by status', 'fw' );
	echo '</caption>';

	echo '<thead><tr>';
	echo '<th scope="col">' . esc_html__( 'Post type', 'fw' ) . '</th>';
	foreach ( $statuses as $status_key => $label ) {
		echo '<th scope="col" class="num">' . esc_html( $label ) . '</th>';
	}
	echo '<th scope="col" class="num">' . esc_html__( 'Total', 'fw' ) . '</th>';
	echo '</tr></thead><tbody>';

	foreach ( $post_types as $pt ) {
		// Optional: skip Media attachments. Remove if you want to include.
		if ( 'attachment' === $pt->name ) {
			continue;
		}

		$pt_obj = get_post_type_object( $pt->name );
		if ( ! $pt_obj ) {
			continue;
		}

		$counts_obj = wp_count_posts( $pt->name );
		$total      = 0;

		echo '<tr>';

		echo '<th scope="row">';
		echo esc_html( $pt->labels->singular_name );
		echo '<div class="pt-slug">' . esc_html( $pt->name ) . '</div>';
		echo '</th>';

		foreach ( $statuses as $status_key => $label ) {
			$count = 0;
			if ( is_object( $counts_obj ) && isset( $counts_obj->{$status_key} ) ) {
				$count = (int) $counts_obj->{$status_key};
			}
			$total += $count;

			// If user can see/edit this post type, link counts to the filtered list.
			$can_access = current_user_can( $pt_obj->cap->edit_posts );

			$link = admin_url(
				'edit.php?post_type=' . rawurlencode( $pt->name ) .
				'&post_status=' . rawurlencode( $status_key )
			);

			// Make link text accessible: include post type + status + count (visually still just the number).
			$aria = sprintf(
				/* translators: 1: count, 2: post type singular label, 3: status label */
				__( '%1$s %2$s items with status %3$s', 'fw' ),
				number_format_i18n( $count ),
				$pt->labels->singular_name,
				$label
			);

			echo '<td class="num">';
			if ( $can_access ) {
				echo '<a href="' . esc_url( $link ) . '" aria-label="' . esc_attr( $aria ) . '">';
				echo esc_html( number_format_i18n( $count ) );
				echo '</a>';
			} else {
				echo esc_html( number_format_i18n( $count ) );
			}
			echo '</td>';
		}

		// Total column (links to "All" list table).
		$all_link = admin_url( 'edit.php?post_type=' . rawurlencode( $pt->name ) . '&all_posts=1' );
		$aria_total = sprintf(
			/* translators: 1: total count, 2: post type singular label */
			__( '%1$s total %2$s items (all statuses)', 'fw' ),
			number_format_i18n( $total ),
			$pt->labels->singular_name
		);

		echo '<td class="num"><strong>';
		if ( current_user_can( $pt_obj->cap->edit_posts ) ) {
			echo '<a href="' . esc_url( $all_link ) . '" aria-label="' . esc_attr( $aria_total ) . '">';
			echo esc_html( number_format_i18n( $total ) );
			echo '</a>';
		} else {
			echo esc_html( number_format_i18n( $total ) );
		}
		echo '</strong></td>';

		echo '</tr>';
	}

	echo '</tbody></table>';
	echo '</div>';
}