pagination.php

I’ll build this as a reusable, safe WordPress template-part component for your RX Theme, with numeric pagination, previous/next links, archive/search support, accessibility, schema-ready markup, and helper fallbacks.

Below is a large advanced pagination.php component for your RX Theme.

Place this file here:

rx-theme/template-parts/components/pagination.php

Use it in templates like:

<?php get_template_part( 'template-parts/components/pagination' ); ?>

Or with custom args:

<?php
get_template_part(
	'template-parts/components/pagination',
	null,
	array(
		'query'      => $custom_query,
		'style'      => 'numbers',
		'show_info'  => true,
		'show_first' => true,
		'show_last'  => true,
	)
);
?>

template-parts/components/pagination.php

<?php
/**
 * RX Theme Advanced Pagination Component
 *
 * File: template-parts/components/pagination.php
 *
 * Features:
 * - Main query pagination support
 * - Custom WP_Query support via $args['query']
 * - Numeric pagination
 * - Previous / Next links
 * - First / Last links
 * - Page info text
 * - Posts count info
 * - Search/archive friendly
 * - Accessibility ready
 * - SEO-friendly rel attributes
 * - Schema-friendly nav wrapper
 * - Safe escaping
 * - RTL-friendly arrow support
 * - WooCommerce-friendly fallback support
 * - AJAX-ready data attributes
 * - Infinite-scroll-ready hooks/classes
 * - Load-more-ready structure
 *
 * Usage:
 *
 * Basic:
 * get_template_part( 'template-parts/components/pagination' );
 *
 * Custom:
 * get_template_part(
 *     'template-parts/components/pagination',
 *     null,
 *     array(
 *         'query'      => $custom_query,
 *         'style'      => 'numbers',
 *         'show_info'  => true,
 *         'show_first' => true,
 *         'show_last'  => true,
 *     )
 * );
 *
 * @package RX_Theme
 */

defined( 'ABSPATH' ) || exit;

/**
 * ------------------------------------------------------------
 * 1. Safe default arguments
 * ------------------------------------------------------------
 */

$rx_pagination_defaults = array(
	/**
	 * Query object.
	 *
	 * Accepts:
	 * - null: use global $wp_query
	 * - WP_Query object
	 */
	'query' => null,

	/**
	 * Pagination style.
	 *
	 * Supported:
	 * - numbers
	 * - simple
	 * - compact
	 * - load_more_markup
	 */
	'style' => 'numbers',

	/**
	 * Wrapper options.
	 */
	'wrapper_class'     => 'rx-pagination',
	'inner_class'       => 'rx-pagination__inner',
	'list_class'        => 'rx-pagination__list',
	'item_class'        => 'rx-pagination__item',
	'link_class'        => 'rx-pagination__link',
	'current_class'     => 'rx-pagination__current',
	'disabled_class'    => 'rx-pagination__disabled',
	'nav_label'         => esc_html__( 'Posts navigation', 'rx-theme' ),
	'aria_label'        => esc_html__( 'Pagination', 'rx-theme' ),
	'container_id'      => '',
	'extra_attributes'  => array(),

	/**
	 * Text labels.
	 */
	'previous_text'     => esc_html__( 'Previous', 'rx-theme' ),
	'next_text'         => esc_html__( 'Next', 'rx-theme' ),
	'first_text'        => esc_html__( 'First', 'rx-theme' ),
	'last_text'         => esc_html__( 'Last', 'rx-theme' ),
	'page_text'         => esc_html__( 'Page', 'rx-theme' ),
	'of_text'           => esc_html__( 'of', 'rx-theme' ),
	'posts_text'        => esc_html__( 'posts', 'rx-theme' ),
	'load_more_text'    => esc_html__( 'Load More', 'rx-theme' ),
	'no_more_text'      => esc_html__( 'No more posts', 'rx-theme' ),

	/**
	 * Icons.
	 */
	'show_icons'        => true,
	'previous_icon_ltr' => '&larr;',
	'next_icon_ltr'     => '&rarr;',
	'previous_icon_rtl' => '&rarr;',
	'next_icon_rtl'     => '&larr;',

	/**
	 * Display controls.
	 */
	'show_previous'     => true,
	'show_next'         => true,
	'show_first'        => true,
	'show_last'         => true,
	'show_numbers'      => true,
	'show_info'         => true,
	'show_post_count'   => true,
	'show_if_single'    => false,
	'show_dots'         => true,

	/**
	 * Number options.
	 */
	'mid_size'          => 2,
	'end_size'          => 1,

	/**
	 * Behavior options.
	 */
	'add_rel'           => true,
	'add_schema'        => true,
	'ajax_ready'        => true,
	'infinite_ready'    => true,
	'load_more_ready'   => true,
	'scroll_to_top'     => false,

	/**
	 * Query vars.
	 */
	'paged_var'         => 'paged',

	/**
	 * Extra CSS utility classes.
	 */
	'alignment'         => 'center', // left, center, right, between
	'size'              => 'md',     // sm, md, lg
	'rounded'           => true,
	'bordered'          => true,
	'shadow'            => false,

	/**
	 * Developer hooks.
	 */
	'before'            => '',
	'after'             => '',
);

$rx_pagination_args = isset( $args ) && is_array( $args )
	? wp_parse_args( $args, $rx_pagination_defaults )
	: $rx_pagination_defaults;

/**
 * Allow child themes/plugins to filter pagination args.
 */
$rx_pagination_args = apply_filters( 'rx_theme_pagination_args', $rx_pagination_args );

/**
 * ------------------------------------------------------------
 * 2. Query detection
 * ------------------------------------------------------------
 */

global $wp_query;

$rx_query = null;

if ( isset( $rx_pagination_args['query'] ) && $rx_pagination_args['query'] instanceof WP_Query ) {
	$rx_query = $rx_pagination_args['query'];
} else {
	$rx_query = $wp_query;
}

if ( ! $rx_query instanceof WP_Query ) {
	return;
}

/**
 * ------------------------------------------------------------
 * 3. Current page and total page detection
 * ------------------------------------------------------------
 */

$rx_total_pages = isset( $rx_query->max_num_pages ) ? absint( $rx_query->max_num_pages ) : 1;

$rx_current_page = max(
	1,
	absint(
		get_query_var( 'paged' )
			? get_query_var( 'paged' )
			: get_query_var( 'page' )
	)
);

/**
 * Fix custom query page detection.
 */
if ( isset( $rx_pagination_args['current'] ) && absint( $rx_pagination_args['current'] ) > 0 ) {
	$rx_current_page = absint( $rx_pagination_args['current'] );
}

/**
 * Do not show pagination if there is only one page,
 * unless show_if_single is enabled.
 */
if ( $rx_total_pages <= 1 && empty( $rx_pagination_args['show_if_single'] ) ) {
	return;
}

/**
 * ------------------------------------------------------------
 * 4. Pagination URL base
 * ------------------------------------------------------------
 */

$rx_big = 999999999;

/**
 * Standard WordPress pagination base.
 */
$rx_base = str_replace(
	$rx_big,
	'%#%',
	esc_url_raw(
		get_pagenum_link( $rx_big )
	)
);

/**
 * Custom base support.
 */
if ( ! empty( $rx_pagination_args['base'] ) ) {
	$rx_base = esc_url_raw( $rx_pagination_args['base'] );
}

/**
 * Pagination format.
 */
$rx_format = '';

if ( get_option( 'permalink_structure' ) ) {
	$rx_format = 'page/%#%/';
} else {
	$rx_format = '?paged=%#%';
}

if ( ! empty( $rx_pagination_args['format'] ) ) {
	$rx_format = sanitize_text_field( $rx_pagination_args['format'] );
}

/**
 * ------------------------------------------------------------
 * 5. Utility values
 * ------------------------------------------------------------
 */

$rx_is_rtl        = is_rtl();
$rx_previous_icon = $rx_is_rtl ? $rx_pagination_args['previous_icon_rtl'] : $rx_pagination_args['previous_icon_ltr'];
$rx_next_icon     = $rx_is_rtl ? $rx_pagination_args['next_icon_rtl'] : $rx_pagination_args['next_icon_ltr'];

$rx_previous_page = max( 1, $rx_current_page - 1 );
$rx_next_page     = min( $rx_total_pages, $rx_current_page + 1 );

$rx_has_previous  = $rx_current_page > 1;
$rx_has_next      = $rx_current_page < $rx_total_pages;

$rx_found_posts   = isset( $rx_query->found_posts ) ? absint( $rx_query->found_posts ) : 0;
$rx_posts_per_page = absint( $rx_query->get( 'posts_per_page' ) );

if ( $rx_posts_per_page <= 0 ) {
	$rx_posts_per_page = absint( get_option( 'posts_per_page' ) );
}

$rx_start_post = 0;
$rx_end_post   = 0;

if ( $rx_found_posts > 0 && $rx_posts_per_page > 0 ) {
	$rx_start_post = ( ( $rx_current_page - 1 ) * $rx_posts_per_page ) + 1;
	$rx_end_post   = min( $rx_current_page * $rx_posts_per_page, $rx_found_posts );
}

/**
 * ------------------------------------------------------------
 * 6. CSS class builder
 * ------------------------------------------------------------
 */

$rx_wrapper_classes = array(
	$rx_pagination_args['wrapper_class'],
	'rx-pagination--style-' . sanitize_html_class( $rx_pagination_args['style'] ),
	'rx-pagination--align-' . sanitize_html_class( $rx_pagination_args['alignment'] ),
	'rx-pagination--size-' . sanitize_html_class( $rx_pagination_args['size'] ),
);

if ( ! empty( $rx_pagination_args['rounded'] ) ) {
	$rx_wrapper_classes[] = 'rx-pagination--rounded';
}

if ( ! empty( $rx_pagination_args['bordered'] ) ) {
	$rx_wrapper_classes[] = 'rx-pagination--bordered';
}

if ( ! empty( $rx_pagination_args['shadow'] ) ) {
	$rx_wrapper_classes[] = 'rx-pagination--shadow';
}

if ( ! empty( $rx_pagination_args['ajax_ready'] ) ) {
	$rx_wrapper_classes[] = 'rx-pagination--ajax-ready';
}

if ( ! empty( $rx_pagination_args['infinite_ready'] ) ) {
	$rx_wrapper_classes[] = 'rx-pagination--infinite-ready';
}

if ( ! empty( $rx_pagination_args['load_more_ready'] ) ) {
	$rx_wrapper_classes[] = 'rx-pagination--load-more-ready';
}

$rx_wrapper_classes = array_filter( array_map( 'sanitize_html_class', $rx_wrapper_classes ) );

/**
 * ------------------------------------------------------------
 * 7. Attribute builder
 * ------------------------------------------------------------
 */

$rx_attributes = array(
	'class'             => implode( ' ', $rx_wrapper_classes ),
	'aria-label'        => $rx_pagination_args['aria_label'],
	'data-current-page' => $rx_current_page,
	'data-total-pages'  => $rx_total_pages,
	'data-found-posts'  => $rx_found_posts,
);

if ( ! empty( $rx_pagination_args['container_id'] ) ) {
	$rx_attributes['id'] = sanitize_html_class( $rx_pagination_args['container_id'] );
}

if ( ! empty( $rx_pagination_args['scroll_to_top'] ) ) {
	$rx_attributes['data-scroll-to-top'] = 'true';
}

if ( ! empty( $rx_pagination_args['extra_attributes'] ) && is_array( $rx_pagination_args['extra_attributes'] ) ) {
	foreach ( $rx_pagination_args['extra_attributes'] as $rx_attr_key => $rx_attr_value ) {
		$rx_attr_key = sanitize_key( $rx_attr_key );

		if ( '' !== $rx_attr_key && ! isset( $rx_attributes[ $rx_attr_key ] ) ) {
			$rx_attributes[ $rx_attr_key ] = sanitize_text_field( $rx_attr_value );
		}
	}
}

$rx_attribute_html = '';

foreach ( $rx_attributes as $rx_attr_key => $rx_attr_value ) {
	if ( '' === $rx_attr_value || null === $rx_attr_value ) {
		continue;
	}

	$rx_attribute_html .= sprintf(
		' %1$s="%2$s"',
		esc_attr( $rx_attr_key ),
		esc_attr( $rx_attr_value )
	);
}

/**
 * ------------------------------------------------------------
 * 8. Helper function fallback
 * ------------------------------------------------------------
 */

if ( ! function_exists( 'rx_theme_pagination_url' ) ) {
	/**
	 * Build pagination page URL.
	 *
	 * @param int    $page Page number.
	 * @param string $base Pagination base.
	 * @param string $format Pagination format.
	 * @return string
	 */
	function rx_theme_pagination_url( $page, $base, $format = '' ) {
		$page = max( 1, absint( $page ) );

		if ( 1 === $page ) {
			$url = get_pagenum_link( 1 );
		} else {
			$url = str_replace( '%#%', $page, $base );
		}

		return esc_url( $url );
	}
}

if ( ! function_exists( 'rx_theme_pagination_rel' ) ) {
	/**
	 * Get rel attribute for pagination links.
	 *
	 * @param int $page Current link page.
	 * @param int $current Current page.
	 * @param int $total Total pages.
	 * @return string
	 */
	function rx_theme_pagination_rel( $page, $current, $total ) {
		$page    = absint( $page );
		$current = absint( $current );
		$total   = absint( $total );

		if ( $page === $current - 1 ) {
			return 'prev';
		}

		if ( $page === $current + 1 ) {
			return 'next';
		}

		return '';
	}
}

if ( ! function_exists( 'rx_theme_pagination_item' ) ) {
	/**
	 * Print one pagination item.
	 *
	 * @param array $item Item args.
	 * @return void
	 */
	function rx_theme_pagination_item( $item ) {
		$defaults = array(
			'type'          => 'number',
			'url'           => '',
			'label'         => '',
			'aria_label'    => '',
			'class'         => '',
			'link_class'    => '',
			'current'       => false,
			'disabled'      => false,
			'rel'           => '',
			'page'          => 0,
			'item_class'    => 'rx-pagination__item',
			'current_class' => 'rx-pagination__current',
			'disabled_class'=> 'rx-pagination__disabled',
		);

		$item = wp_parse_args( $item, $defaults );

		$item_classes = array(
			$item['item_class'],
			'rx-pagination__item--' . sanitize_html_class( $item['type'] ),
		);

		if ( ! empty( $item['class'] ) ) {
			$item_classes[] = sanitize_html_class( $item['class'] );
		}

		if ( ! empty( $item['current'] ) ) {
			$item_classes[] = sanitize_html_class( $item['current_class'] );
			$item_classes[] = 'is-current';
		}

		if ( ! empty( $item['disabled'] ) ) {
			$item_classes[] = sanitize_html_class( $item['disabled_class'] );
			$item_classes[] = 'is-disabled';
		}

		$item_classes = array_filter( $item_classes );

		echo '<li class="' . esc_attr( implode( ' ', $item_classes ) ) . '">';

		if ( ! empty( $item['current'] ) ) {
			echo '<span class="' . esc_attr( $item['link_class'] ) . '" aria-current="page">';
			echo wp_kses_post( $item['label'] );
			echo '</span>';
		} elseif ( ! empty( $item['disabled'] ) || empty( $item['url'] ) ) {
			echo '<span class="' . esc_attr( $item['link_class'] ) . '" aria-disabled="true">';
			echo wp_kses_post( $item['label'] );
			echo '</span>';
		} else {
			$rel_html = '';

			if ( ! empty( $item['rel'] ) ) {
				$rel_html = ' rel="' . esc_attr( $item['rel'] ) . '"';
			}

			$page_html = '';

			if ( ! empty( $item['page'] ) ) {
				$page_html = ' data-page="' . esc_attr( absint( $item['page'] ) ) . '"';
			}

			echo '<a class="' . esc_attr( $item['link_class'] ) . '" href="' . esc_url( $item['url'] ) . '"' . $rel_html . $page_html . ' aria-label="' . esc_attr( $item['aria_label'] ) . '">';
			echo wp_kses_post( $item['label'] );
			echo '</a>';
		}

		echo '</li>';
	}
}

/**
 * ------------------------------------------------------------
 * 9. Build numeric pagination array
 * ------------------------------------------------------------
 */

$rx_number_links = paginate_links(
	array(
		'base'      => $rx_base,
		'format'    => $rx_format,
		'current'   => $rx_current_page,
		'total'     => $rx_total_pages,
		'mid_size'  => absint( $rx_pagination_args['mid_size'] ),
		'end_size'  => absint( $rx_pagination_args['end_size'] ),
		'type'      => 'array',
		'prev_next' => false,
	)
);

if ( ! is_array( $rx_number_links ) ) {
	$rx_number_links = array();
}

/**
 * ------------------------------------------------------------
 * 10. Schema attributes
 * ------------------------------------------------------------
 */

$rx_schema_html = '';

if ( ! empty( $rx_pagination_args['add_schema'] ) ) {
	$rx_schema_html = ' itemscope itemtype="https://schema.org/SiteNavigationElement"';
}

/**
 * ------------------------------------------------------------
 * 11. Developer hook before render
 * ------------------------------------------------------------
 */

do_action(
	'rx_theme_before_pagination',
	$rx_pagination_args,
	$rx_query,
	$rx_current_page,
	$rx_total_pages
);

echo wp_kses_post( $rx_pagination_args['before'] );

?>

<nav<?php echo $rx_attribute_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><?php echo $rx_schema_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
	<div class="<?php echo esc_attr( $rx_pagination_args['inner_class'] ); ?>">

		<?php if ( ! empty( $rx_pagination_args['show_info'] ) || ! empty( $rx_pagination_args['show_post_count'] ) ) : ?>
			<div class="rx-pagination__meta">

				<?php if ( ! empty( $rx_pagination_args['show_info'] ) ) : ?>
					<p class="rx-pagination__page-info">
						<span class="rx-pagination__page-info-label">
							<?php echo esc_html( $rx_pagination_args['page_text'] ); ?>
						</span>

						<span class="rx-pagination__page-info-current">
							<?php echo esc_html( number_format_i18n( $rx_current_page ) ); ?>
						</span>

						<span class="rx-pagination__page-info-of">
							<?php echo esc_html( $rx_pagination_args['of_text'] ); ?>
						</span>

						<span class="rx-pagination__page-info-total">
							<?php echo esc_html( number_format_i18n( $rx_total_pages ) ); ?>
						</span>
					</p>
				<?php endif; ?>

				<?php if ( ! empty( $rx_pagination_args['show_post_count'] ) && $rx_found_posts > 0 ) : ?>
					<p class="rx-pagination__post-count">
						<span class="rx-pagination__post-count-range">
							<?php
							echo esc_html(
								sprintf(
									/* translators: 1: first post number, 2: last post number */
									__( 'Showing %1$s–%2$s', 'rx-theme' ),
									number_format_i18n( $rx_start_post ),
									number_format_i18n( $rx_end_post )
								)
							);
							?>
						</span>

						<span class="rx-pagination__post-count-total">
							<?php
							echo esc_html(
								sprintf(
									/* translators: 1: total posts, 2: posts text */
									__( 'of %1$s %2$s', 'rx-theme' ),
									number_format_i18n( $rx_found_posts ),
									$rx_pagination_args['posts_text']
								)
							);
							?>
						</span>
					</p>
				<?php endif; ?>

			</div>
		<?php endif; ?>

		<?php if ( 'simple' === $rx_pagination_args['style'] ) : ?>

			<div class="rx-pagination__simple">

				<?php if ( ! empty( $rx_pagination_args['show_previous'] ) ) : ?>
					<?php if ( $rx_has_previous ) : ?>
						<a
							class="rx-pagination__simple-link rx-pagination__simple-link--previous"
							href="<?php echo esc_url( rx_theme_pagination_url( $rx_previous_page, $rx_base, $rx_format ) ); ?>"
							rel="prev"
							data-page="<?php echo esc_attr( $rx_previous_page ); ?>"
							aria-label="<?php echo esc_attr__( 'Go to previous page', 'rx-theme' ); ?>"
						>
							<?php if ( ! empty( $rx_pagination_args['show_icons'] ) ) : ?>
								<span class="rx-pagination__icon" aria-hidden="true"><?php echo wp_kses_post( $rx_previous_icon ); ?></span>
							<?php endif; ?>
							<span><?php echo esc_html( $rx_pagination_args['previous_text'] ); ?></span>
						</a>
					<?php else : ?>
						<span class="rx-pagination__simple-link rx-pagination__simple-link--previous is-disabled" aria-disabled="true">
							<?php if ( ! empty( $rx_pagination_args['show_icons'] ) ) : ?>
								<span class="rx-pagination__icon" aria-hidden="true"><?php echo wp_kses_post( $rx_previous_icon ); ?></span>
							<?php endif; ?>
							<span><?php echo esc_html( $rx_pagination_args['previous_text'] ); ?></span>
						</span>
					<?php endif; ?>
				<?php endif; ?>

				<?php if ( ! empty( $rx_pagination_args['show_next'] ) ) : ?>
					<?php if ( $rx_has_next ) : ?>
						<a
							class="rx-pagination__simple-link rx-pagination__simple-link--next"
							href="<?php echo esc_url( rx_theme_pagination_url( $rx_next_page, $rx_base, $rx_format ) ); ?>"
							rel="next"
							data-page="<?php echo esc_attr( $rx_next_page ); ?>"
							aria-label="<?php echo esc_attr__( 'Go to next page', 'rx-theme' ); ?>"
						>
							<span><?php echo esc_html( $rx_pagination_args['next_text'] ); ?></span>
							<?php if ( ! empty( $rx_pagination_args['show_icons'] ) ) : ?>
								<span class="rx-pagination__icon" aria-hidden="true"><?php echo wp_kses_post( $rx_next_icon ); ?></span>
							<?php endif; ?>
						</a>
					<?php else : ?>
						<span class="rx-pagination__simple-link rx-pagination__simple-link--next is-disabled" aria-disabled="true">
							<span><?php echo esc_html( $rx_pagination_args['next_text'] ); ?></span>
							<?php if ( ! empty( $rx_pagination_args['show_icons'] ) ) : ?>
								<span class="rx-pagination__icon" aria-hidden="true"><?php echo wp_kses_post( $rx_next_icon ); ?></span>
							<?php endif; ?>
						</span>
					<?php endif; ?>
				<?php endif; ?>

			</div>

		<?php elseif ( 'compact' === $rx_pagination_args['style'] ) : ?>

			<div class="rx-pagination__compact">

				<?php if ( $rx_has_previous ) : ?>
					<a
						class="rx-pagination__compact-button rx-pagination__compact-button--previous"
						href="<?php echo esc_url( rx_theme_pagination_url( $rx_previous_page, $rx_base, $rx_format ) ); ?>"
						rel="prev"
						data-page="<?php echo esc_attr( $rx_previous_page ); ?>"
						aria-label="<?php echo esc_attr__( 'Previous page', 'rx-theme' ); ?>"
					>
						<?php echo wp_kses_post( $rx_previous_icon ); ?>
					</a>
				<?php else : ?>
					<span class="rx-pagination__compact-button rx-pagination__compact-button--previous is-disabled" aria-disabled="true">
						<?php echo wp_kses_post( $rx_previous_icon ); ?>
					</span>
				<?php endif; ?>

				<span class="rx-pagination__compact-status">
					<?php
					echo esc_html(
						sprintf(
							/* translators: 1: current page, 2: total pages */
							__( '%1$s / %2$s', 'rx-theme' ),
							number_format_i18n( $rx_current_page ),
							number_format_i18n( $rx_total_pages )
						)
					);
					?>
				</span>

				<?php if ( $rx_has_next ) : ?>
					<a
						class="rx-pagination__compact-button rx-pagination__compact-button--next"
						href="<?php echo esc_url( rx_theme_pagination_url( $rx_next_page, $rx_base, $rx_format ) ); ?>"
						rel="next"
						data-page="<?php echo esc_attr( $rx_next_page ); ?>"
						aria-label="<?php echo esc_attr__( 'Next page', 'rx-theme' ); ?>"
					>
						<?php echo wp_kses_post( $rx_next_icon ); ?>
					</a>
				<?php else : ?>
					<span class="rx-pagination__compact-button rx-pagination__compact-button--next is-disabled" aria-disabled="true">
						<?php echo wp_kses_post( $rx_next_icon ); ?>
					</span>
				<?php endif; ?>

			</div>

		<?php elseif ( 'load_more_markup' === $rx_pagination_args['style'] ) : ?>

			<div class="rx-pagination__load-more-wrap">

				<?php if ( $rx_has_next ) : ?>
					<a
						class="rx-pagination__load-more-button"
						href="<?php echo esc_url( rx_theme_pagination_url( $rx_next_page, $rx_base, $rx_format ) ); ?>"
						rel="next"
						data-page="<?php echo esc_attr( $rx_next_page ); ?>"
						data-current-page="<?php echo esc_attr( $rx_current_page ); ?>"
						data-total-pages="<?php echo esc_attr( $rx_total_pages ); ?>"
						aria-label="<?php echo esc_attr__( 'Load more posts', 'rx-theme' ); ?>"
					>
						<span class="rx-pagination__load-more-text">
							<?php echo esc_html( $rx_pagination_args['load_more_text'] ); ?>
						</span>

						<span class="rx-pagination__load-more-icon" aria-hidden="true">
							+
						</span>
					</a>
				<?php else : ?>
					<span class="rx-pagination__load-more-button is-disabled" aria-disabled="true">
						<span class="rx-pagination__load-more-text">
							<?php echo esc_html( $rx_pagination_args['no_more_text'] ); ?>
						</span>
					</span>
				<?php endif; ?>

			</div>

		<?php else : ?>

			<ul class="<?php echo esc_attr( $rx_pagination_args['list_class'] ); ?>">

				<?php
				/**
				 * First page link.
				 */
				if ( ! empty( $rx_pagination_args['show_first'] ) ) {
					rx_theme_pagination_item(
						array(
							'type'           => 'first',
							'url'            => $rx_has_previous ? rx_theme_pagination_url( 1, $rx_base, $rx_format ) : '',
							'label'          => esc_html( $rx_pagination_args['first_text'] ),
							'aria_label'     => esc_html__( 'Go to first page', 'rx-theme' ),
							'link_class'     => $rx_pagination_args['link_class'],
							'disabled'       => ! $rx_has_previous,
							'page'           => 1,
							'item_class'     => $rx_pagination_args['item_class'],
							'current_class'  => $rx_pagination_args['current_class'],
							'disabled_class' => $rx_pagination_args['disabled_class'],
						)
					);
				}

				/**
				 * Previous page link.
				 */
				if ( ! empty( $rx_pagination_args['show_previous'] ) ) {
					$rx_previous_label = '';

					if ( ! empty( $rx_pagination_args['show_icons'] ) ) {
						$rx_previous_label .= '<span class="rx-pagination__icon" aria-hidden="true">' . wp_kses_post( $rx_previous_icon ) . '</span> ';
					}

					$rx_previous_label .= '<span class="rx-pagination__text">' . esc_html( $rx_pagination_args['previous_text'] ) . '</span>';

					rx_theme_pagination_item(
						array(
							'type'           => 'previous',
							'url'            => $rx_has_previous ? rx_theme_pagination_url( $rx_previous_page, $rx_base, $rx_format ) : '',
							'label'          => $rx_previous_label,
							'aria_label'     => esc_html__( 'Go to previous page', 'rx-theme' ),
							'link_class'     => $rx_pagination_args['link_class'],
							'disabled'       => ! $rx_has_previous,
							'rel'            => 'prev',
							'page'           => $rx_previous_page,
							'item_class'     => $rx_pagination_args['item_class'],
							'current_class'  => $rx_pagination_args['current_class'],
							'disabled_class' => $rx_pagination_args['disabled_class'],
						)
					);
				}

				/**
				 * Number links.
				 */
				if ( ! empty( $rx_pagination_args['show_numbers'] ) ) {
					foreach ( $rx_number_links as $rx_link ) {
						$rx_is_current = false;
						$rx_is_dots    = false;
						$rx_page_num   = 0;
						$rx_url        = '';

						if ( false !== strpos( $rx_link, 'current' ) ) {
							$rx_is_current = true;
							$rx_page_num   = $rx_current_page;
						}

						if ( false !== strpos( $rx_link, 'dots' ) ) {
							$rx_is_dots = true;
						}

						if ( preg_match( '/page-numbers[^>]*>([0-9]+)</', $rx_link, $rx_matches ) ) {
							$rx_page_num = absint( $rx_matches[1] );
						}

						if ( preg_match( '/href=[\'"]([^\'"]+)[\'"]/', $rx_link, $rx_url_matches ) ) {
							$rx_url = esc_url_raw( html_entity_decode( $rx_url_matches[1] ) );
						}

						if ( $rx_is_dots ) {
							if ( ! empty( $rx_pagination_args['show_dots'] ) ) {
								rx_theme_pagination_item(
									array(
										'type'           => 'dots',
										'label'          => '&hellip;',
										'aria_label'     => esc_html__( 'Pagination dots', 'rx-theme' ),
										'link_class'     => $rx_pagination_args['link_class'],
										'disabled'       => true,
										'item_class'     => $rx_pagination_args['item_class'],
										'current_class'  => $rx_pagination_args['current_class'],
										'disabled_class' => $rx_pagination_args['disabled_class'],
									)
								);
							}

							continue;
						}

						if ( $rx_page_num <= 0 ) {
							continue;
						}

						$rx_rel = '';

						if ( ! empty( $rx_pagination_args['add_rel'] ) ) {
							$rx_rel = rx_theme_pagination_rel( $rx_page_num, $rx_current_page, $rx_total_pages );
						}

						rx_theme_pagination_item(
							array(
								'type'           => 'number',
								'url'            => $rx_is_current ? '' : $rx_url,
								'label'          => number_format_i18n( $rx_page_num ),
								'aria_label'     => sprintf(
									/* translators: %s: page number */
									esc_html__( 'Go to page %s', 'rx-theme' ),
									number_format_i18n( $rx_page_num )
								),
								'link_class'     => $rx_pagination_args['link_class'],
								'current'        => $rx_is_current,
								'rel'            => $rx_rel,
								'page'           => $rx_page_num,
								'item_class'     => $rx_pagination_args['item_class'],
								'current_class'  => $rx_pagination_args['current_class'],
								'disabled_class' => $rx_pagination_args['disabled_class'],
							)
						);
					}
				}

				/**
				 * Next page link.
				 */
				if ( ! empty( $rx_pagination_args['show_next'] ) ) {
					$rx_next_label = '<span class="rx-pagination__text">' . esc_html( $rx_pagination_args['next_text'] ) . '</span>';

					if ( ! empty( $rx_pagination_args['show_icons'] ) ) {
						$rx_next_label .= ' <span class="rx-pagination__icon" aria-hidden="true">' . wp_kses_post( $rx_next_icon ) . '</span>';
					}

					rx_theme_pagination_item(
						array(
							'type'           => 'next',
							'url'            => $rx_has_next ? rx_theme_pagination_url( $rx_next_page, $rx_base, $rx_format ) : '',
							'label'          => $rx_next_label,
							'aria_label'     => esc_html__( 'Go to next page', 'rx-theme' ),
							'link_class'     => $rx_pagination_args['link_class'],
							'disabled'       => ! $rx_has_next,
							'rel'            => 'next',
							'page'           => $rx_next_page,
							'item_class'     => $rx_pagination_args['item_class'],
							'current_class'  => $rx_pagination_args['current_class'],
							'disabled_class' => $rx_pagination_args['disabled_class'],
						)
					);
				}

				/**
				 * Last page link.
				 */
				if ( ! empty( $rx_pagination_args['show_last'] ) ) {
					rx_theme_pagination_item(
						array(
							'type'           => 'last',
							'url'            => $rx_has_next ? rx_theme_pagination_url( $rx_total_pages, $rx_base, $rx_format ) : '',
							'label'          => esc_html( $rx_pagination_args['last_text'] ),
							'aria_label'     => esc_html__( 'Go to last page', 'rx-theme' ),
							'link_class'     => $rx_pagination_args['link_class'],
							'disabled'       => ! $rx_has_next,
							'page'           => $rx_total_pages,
							'item_class'     => $rx_pagination_args['item_class'],
							'current_class'  => $rx_pagination_args['current_class'],
							'disabled_class' => $rx_pagination_args['disabled_class'],
						)
					);
				}
				?>

			</ul>

		<?php endif; ?>

		<?php if ( ! empty( $rx_pagination_args['infinite_ready'] ) ) : ?>
			<span
				class="rx-pagination__sentinel"
				aria-hidden="true"
				data-rx-pagination-sentinel="true"
				data-next-page="<?php echo esc_attr( $rx_has_next ? $rx_next_page : 0 ); ?>"
				data-total-pages="<?php echo esc_attr( $rx_total_pages ); ?>"
			></span>
		<?php endif; ?>

	</div>
</nav>

<?php

echo wp_kses_post( $rx_pagination_args['after'] );

do_action(
	'rx_theme_after_pagination',
	$rx_pagination_args,
	$rx_query,
	$rx_current_page,
	$rx_total_pages
);

Optional CSS for style.css

Add this to your RX Theme style.css or pagination CSS file:

.rx-pagination {
	margin: 2.5rem 0;
	width: 100%;
}

.rx-pagination__inner {
	display: flex;
	flex-direction: column;
	gap: 1rem;
}

.rx-pagination__meta {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem 1.25rem;
	align-items: center;
	justify-content: center;
	font-size: 0.9375rem;
	color: var(--rx-color-text-muted, #667085);
}

.rx-pagination__page-info,
.rx-pagination__post-count {
	margin: 0;
}

.rx-pagination__list {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
	align-items: center;
	justify-content: center;
	margin: 0;
	padding: 0;
	list-style: none;
}

.rx-pagination--align-left .rx-pagination__list,
.rx-pagination--align-left .rx-pagination__meta {
	justify-content: flex-start;
}

.rx-pagination--align-center .rx-pagination__list,
.rx-pagination--align-center .rx-pagination__meta {
	justify-content: center;
}

.rx-pagination--align-right .rx-pagination__list,
.rx-pagination--align-right .rx-pagination__meta {
	justify-content: flex-end;
}

.rx-pagination--align-between .rx-pagination__list,
.rx-pagination--align-between .rx-pagination__meta {
	justify-content: space-between;
}

.rx-pagination__item {
	display: inline-flex;
}

.rx-pagination__link,
.rx-pagination__current,
.rx-pagination__disabled,
.rx-pagination__simple-link,
.rx-pagination__compact-button,
.rx-pagination__load-more-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.4rem;
	min-width: 2.5rem;
	min-height: 2.5rem;
	padding: 0.55rem 0.85rem;
	border: 1px solid var(--rx-color-border, #d0d5dd);
	border-radius: 0.5rem;
	background: var(--rx-color-surface, #ffffff);
	color: var(--rx-color-text, #101828);
	font-size: 0.9375rem;
	font-weight: 600;
	line-height: 1;
	text-decoration: none;
	transition:
		background-color 0.2s ease,
		color 0.2s ease,
		border-color 0.2s ease,
		box-shadow 0.2s ease,
		transform 0.2s ease;
}

.rx-pagination__link:hover,
.rx-pagination__simple-link:hover,
.rx-pagination__compact-button:hover,
.rx-pagination__load-more-button:hover {
	background: var(--rx-color-primary, #2563eb);
	border-color: var(--rx-color-primary, #2563eb);
	color: #ffffff;
	text-decoration: none;
	transform: translateY(-1px);
}

.rx-pagination__link:focus-visible,
.rx-pagination__simple-link:focus-visible,
.rx-pagination__compact-button:focus-visible,
.rx-pagination__load-more-button:focus-visible {
	outline: 3px solid rgba(37, 99, 235, 0.25);
	outline-offset: 2px;
}

.rx-pagination__current,
.rx-pagination .is-current > span {
	background: var(--rx-color-primary, #2563eb);
	border-color: var(--rx-color-primary, #2563eb);
	color: #ffffff;
	cursor: default;
}

.rx-pagination__disabled,
.rx-pagination .is-disabled > span,
.rx-pagination .is-disabled {
	opacity: 0.45;
	cursor: not-allowed;
	pointer-events: none;
}

.rx-pagination--size-sm .rx-pagination__link,
.rx-pagination--size-sm .rx-pagination__current,
.rx-pagination--size-sm .rx-pagination__disabled {
	min-width: 2rem;
	min-height: 2rem;
	padding: 0.4rem 0.65rem;
	font-size: 0.8125rem;
}

.rx-pagination--size-lg .rx-pagination__link,
.rx-pagination--size-lg .rx-pagination__current,
.rx-pagination--size-lg .rx-pagination__disabled {
	min-width: 3rem;
	min-height: 3rem;
	padding: 0.75rem 1rem;
	font-size: 1rem;
}

.rx-pagination--rounded .rx-pagination__link,
.rx-pagination--rounded .rx-pagination__current,
.rx-pagination--rounded .rx-pagination__disabled,
.rx-pagination--rounded .rx-pagination__simple-link,
.rx-pagination--rounded .rx-pagination__compact-button,
.rx-pagination--rounded .rx-pagination__load-more-button {
	border-radius: 999px;
}

.rx-pagination--shadow .rx-pagination__link,
.rx-pagination--shadow .rx-pagination__current,
.rx-pagination--shadow .rx-pagination__disabled,
.rx-pagination--shadow .rx-pagination__simple-link,
.rx-pagination--shadow .rx-pagination__compact-button,
.rx-pagination--shadow .rx-pagination__load-more-button {
	box-shadow: 0 8px 20px rgba(16, 24, 40, 0.08);
}

.rx-pagination__simple,
.rx-pagination__compact,
.rx-pagination__load-more-wrap {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
	align-items: center;
	justify-content: center;
}

.rx-pagination__compact-status {
	font-weight: 700;
	color: var(--rx-color-text, #101828);
}

.rx-pagination__load-more-button {
	min-width: 11rem;
}

.rx-pagination__sentinel {
	display: block;
	width: 100%;
	height: 1px;
	pointer-events: none;
}

@media (max-width: 575px) {
	.rx-pagination__meta {
		flex-direction: column;
		gap: 0.35rem;
		text-align: center;
	}

	.rx-pagination__list {
		gap: 0.35rem;
	}

	.rx-pagination__item--first,
	.rx-pagination__item--last {
		display: none;
	}

	.rx-pagination__link,
	.rx-pagination__current,
	.rx-pagination__disabled {
		min-width: 2.25rem;
		min-height: 2.25rem;
		padding: 0.45rem 0.65rem;
		font-size: 0.875rem;
	}
}

Example uses

Normal archive pagination

<?php get_template_part( 'template-parts/components/pagination' ); ?>

Compact pagination

<?php
get_template_part(
	'template-parts/components/pagination',
	null,
	array(
		'style' => 'compact',
	)
);
?>

Simple previous / next pagination

<?php
get_template_part(
	'template-parts/components/pagination',
	null,
	array(
		'style'           => 'simple',
		'show_info'       => false,
		'show_post_count' => false,
	)
);
?>

Load more button markup

<?php
get_template_part(
	'template-parts/components/pagination',
	null,
	array(
		'style'           => 'load_more_markup',
		'show_info'       => true,
		'show_post_count' => true,
	)
);
?>

Custom WP_Query pagination

<?php
$rx_custom_query = new WP_Query(
	array(
		'post_type'      => 'post',
		'posts_per_page' => 6,
		'paged'          => max( 1, get_query_var( 'paged' ) ),
	)
);

if ( $rx_custom_query->have_posts() ) :
	while ( $rx_custom_query->have_posts() ) :
		$rx_custom_query->the_post();

		get_template_part( 'template-parts/content/content', get_post_type() );

	endwhile;

	get_template_part(
		'template-parts/components/pagination',
		null,
		array(
			'query'      => $rx_custom_query,
			'style'      => 'numbers',
			'show_first' => true,
			'show_last'  => true,
		)
	);

	wp_reset_postdata();
endif;
?>

This component is safe to use as your main RX Theme pagination system.

Leave a Reply

Your email address will not be published. Required fields are marked *