
WordPress provides the paginate_links() function to generate paginated navigation for archive pages. It works well for many cases, but there are a couple of limitations I wanted to address:
- The
end_sizeparameter controls how many of the first and last pages are always displayed (default is 1). There’s no way to setend_size = 0, so when you are several pages deep you still get the first page and the last page shown with ellipses separating them from the surrounding links. - The
mid_sizeparameter sets how many links appear on each side of the current page. I wanted independent control so more pages after the current page are visible; in other words, a pagination that emphasizes the current page and the subsequent pages, rather than symmetrically showing pages before and after.
These may seem like small tweaks, but they significantly reduce the visual variability of the pagination and make it easier to style consistently.
Below is what paginate_links() typically outputs on the first page of a category archive:

And this is how it can appear on the fifth page:

That layout can consume a lot of space and distract users from the most important controls: the current page, and the previous/next links.
Varying navigation length also makes styling harder, especially on mobile where inconsistent line breaks can break the layout. For example, on small screens the standard output can appear cluttered:

A better way
I prefer a simpler, more predictable approach: show previous/next arrows, the current page, and a fixed number of pages after the current page. This keeps every archive page showing the same number of links and provides a consistent experience as users browse archives. You can choose a link count that fits on one line on mobile.
Here’s an example of how that navigation can look:

At the top of the example code, modify the $settings array to set the total number of links shown and customize the previous/next text. The code creates a consistent pagination block that prioritizes the current page and the immediate following pages, adds previous/next arrows when appropriate, and inserts an omission marker and the final page link when there are many pages beyond the visible range.
/**
* Archive Navigation
*
* @author Bill Erickson
* @link https://www.billerickson.net/custom-pagination-links/
*
*/
function ea_archive_navigation() {
$settings = array(
'count' => 7,
'prev_text' => be_icon( [ 'icon' => 'arrow-left' ] ),
'next_text' => be_icon( [ 'icon' => 'arrow-right' ] ),
);
global $wp_query;
$current = max( 1, get_query_var( 'paged' ) );
$total = $wp_query->max_num_pages;
$links = array();
// Offset for next link
if( $current < $total )
$settings['count']--;
if( $current + 3 < $total ) {
$settings['count'] = $settings['count'] - 2;
}
// Previous
if( $current > 1 ) {
$settings['count']--;
$links[] = ea_archive_navigation_link( $current - 1, 'pagination-previous', $settings['prev_text'] );
$settings['count']--;
$links[] = ea_archive_navigation_link( $current - 1 );
}
// Current
$links[] = ea_archive_navigation_link( $current, 'active' );
// Next Pages
for( $i = 1; $i < $settings['count']; $i++ ) {
$page = $current + $i;
if( $page <= $total ) {
$links[] = ea_archive_navigation_link( $page );
}
}
// Next
if( $current < $total ) {
if( $current + 3 < $total ) {
$links[] = '… ';
$links[] = ea_archive_navigation_link( $total );
}
$links[] = ea_archive_navigation_link( $current + 1, 'pagination-next', $settings['next_text'] );
}
echo '';
}
add_action( 'tha_content_while_after', 'ea_archive_navigation' );
/**
* Archive Navigation Link
*
* @author Bill Erickson
* @link https://www.billerickson.net/custom-pagination-links/
*
* @param int $page
* @param string $class
* @param string $label
* @return string $link
*/
function ea_archive_navigation_link( $page = false, $class = '', $label = '' ) {
if( ! $page )
return;
$label = $label ? $label : $page;
$link = esc_url_raw( get_pagenum_link( $page ) );
$output = '';
if ( ! empty( $class ) ) {
$output .= '';
} else {
$output .= ' ';
}
$output .= '' . $label . ' ';
return $output;
}