I’ll build this as a full advanced template-parts/post/post-navigation.php file for your RX theme, with previous/next posts, accessibility, thumbnails, categories, dates, reading time support, same-category logic, fallbacks, and safe WordPress escaping.
Yes friend, you can make template-parts/post/post-navigation.php very advanced. WordPress already supports adjacent post navigation with get_previous_post(), get_next_post(), and get_the_post_navigation(), including same-taxonomy filtering, so this file safely builds on native WordPress functions. Featured-image output is also handled by get_the_post_thumbnail().
Create this file:
rx-theme/template-parts/post/post-navigation.php
Then paste this full code:
<?php
/**
* Template Part: Advanced Single Post Navigation
*
* File: template-parts/post/post-navigation.php
*
* Features:
* - Previous / Next post navigation
* - Same-category support
* - Thumbnail support
* - Fallback thumbnail support
* - Category display
* - Date display
* - Author display
* - Reading-time display if RX helper exists
* - Word count display
* - Comment count display
* - Post excerpt preview
* - Mobile-friendly structure
* - Accessibility labels
* - Schema-friendly attributes
* - Custom filter support
* - Safe escaping
*
* @package RX_Theme
*/
defined( 'ABSPATH' ) || exit;
if ( ! is_singular( 'post' ) ) {
return;
}
$current_post_id = get_the_ID();
if ( ! $current_post_id ) {
return;
}
/**
* ------------------------------------------------------------
* Configuration
* ------------------------------------------------------------
*/
$rx_nav_defaults = array(
'same_term' => true,
'taxonomy' => 'category',
'excluded_terms' => '',
'show_thumbnail' => true,
'show_category' => true,
'show_date' => true,
'show_author' => true,
'show_excerpt' => true,
'show_reading_time' => true,
'show_word_count' => true,
'show_comment_count' => true,
'show_direction_icon' => true,
'show_empty_state' => false,
'thumbnail_size' => 'medium_large',
'excerpt_length' => 22,
'fallback_image' => '',
'wrapper_class' => 'rx-post-navigation',
);
/**
* Allow child theme or plugin customization.
*
* Example:
* add_filter( 'rx_post_navigation_args', function( $args ) {
* $args['same_term'] = false;
* return $args;
* } );
*/
$rx_nav_args = apply_filters( 'rx_post_navigation_args', $rx_nav_defaults );
$rx_same_term = ! empty( $rx_nav_args['same_term'] );
$rx_excluded_terms = ! empty( $rx_nav_args['excluded_terms'] ) ? $rx_nav_args['excluded_terms'] : '';
$rx_taxonomy = ! empty( $rx_nav_args['taxonomy'] ) ? sanitize_key( $rx_nav_args['taxonomy'] ) : 'category';
$previous_post = get_previous_post( $rx_same_term, $rx_excluded_terms, $rx_taxonomy );
$next_post = get_next_post( $rx_same_term, $rx_excluded_terms, $rx_taxonomy );
/**
* If same-category navigation has no result, fallback to global adjacent posts.
*/
if ( ! $previous_post && $rx_same_term ) {
$previous_post = get_previous_post( false );
}
if ( ! $next_post && $rx_same_term ) {
$next_post = get_next_post( false );
}
if ( ! $previous_post && ! $next_post && empty( $rx_nav_args['show_empty_state'] ) ) {
return;
}
/**
* ------------------------------------------------------------
* Helper: Get reading time
* ------------------------------------------------------------
*/
if ( ! function_exists( 'rx_post_navigation_get_reading_time' ) ) {
function rx_post_navigation_get_reading_time( $post_id ) {
$post_id = absint( $post_id );
if ( ! $post_id ) {
return '';
}
if ( function_exists( 'rx_get_reading_time' ) ) {
return rx_get_reading_time( $post_id );
}
if ( function_exists( 'rx_reading_time' ) ) {
return rx_reading_time( $post_id );
}
$content = get_post_field( 'post_content', $post_id );
$content = wp_strip_all_tags( strip_shortcodes( $content ) );
$words = str_word_count( $content );
if ( $words <= 0 ) {
return '';
}
$minutes = max( 1, ceil( $words / 200 ) );
return sprintf(
/* translators: %s: reading time in minutes */
_n( '%s min read', '%s min read', $minutes, 'rx-theme' ),
number_format_i18n( $minutes )
);
}
}
/**
* ------------------------------------------------------------
* Helper: Get word count
* ------------------------------------------------------------
*/
if ( ! function_exists( 'rx_post_navigation_get_word_count' ) ) {
function rx_post_navigation_get_word_count( $post_id ) {
$post_id = absint( $post_id );
if ( ! $post_id ) {
return 0;
}
$content = get_post_field( 'post_content', $post_id );
$content = wp_strip_all_tags( strip_shortcodes( $content ) );
return absint( str_word_count( $content ) );
}
}
/**
* ------------------------------------------------------------
* Helper: Get first category name
* ------------------------------------------------------------
*/
if ( ! function_exists( 'rx_post_navigation_get_primary_term' ) ) {
function rx_post_navigation_get_primary_term( $post_id, $taxonomy = 'category' ) {
$post_id = absint( $post_id );
$taxonomy = sanitize_key( $taxonomy );
if ( ! $post_id || ! taxonomy_exists( $taxonomy ) ) {
return '';
}
$terms = get_the_terms( $post_id, $taxonomy );
if ( empty( $terms ) || is_wp_error( $terms ) ) {
return '';
}
$term = array_shift( $terms );
return $term && ! empty( $term->name ) ? $term->name : '';
}
}
/**
* ------------------------------------------------------------
* Helper: Get clean excerpt
* ------------------------------------------------------------
*/
if ( ! function_exists( 'rx_post_navigation_get_excerpt' ) ) {
function rx_post_navigation_get_excerpt( $post_id, $length = 22 ) {
$post_id = absint( $post_id );
$length = absint( $length );
if ( ! $post_id ) {
return '';
}
$excerpt = get_the_excerpt( $post_id );
if ( ! $excerpt ) {
$content = get_post_field( 'post_content', $post_id );
$excerpt = wp_strip_all_tags( strip_shortcodes( $content ) );
}
$excerpt = wp_trim_words( $excerpt, $length, '…' );
return $excerpt;
}
}
/**
* ------------------------------------------------------------
* Helper: Thumbnail
* ------------------------------------------------------------
*/
if ( ! function_exists( 'rx_post_navigation_thumbnail' ) ) {
function rx_post_navigation_thumbnail( $post_id, $size = 'medium_large', $fallback_image = '', $direction = 'next' ) {
$post_id = absint( $post_id );
$size = $size ? $size : 'medium_large';
$fallback_image = esc_url( $fallback_image );
$direction = sanitize_html_class( $direction );
if ( ! $post_id ) {
return '';
}
$title = get_the_title( $post_id );
if ( has_post_thumbnail( $post_id ) ) {
return get_the_post_thumbnail(
$post_id,
$size,
array(
'class' => 'rx-post-nav__image rx-post-nav__image--' . $direction,
'alt' => esc_attr( $title ),
'loading' => 'lazy',
'decoding' => 'async',
)
);
}
if ( $fallback_image ) {
return sprintf(
'<img class="rx-post-nav__image rx-post-nav__image--fallback rx-post-nav__image--%1$s" src="%2$s" alt="%3$s" loading="lazy" decoding="async" />',
esc_attr( $direction ),
esc_url( $fallback_image ),
esc_attr( $title )
);
}
return sprintf(
'<span class="rx-post-nav__placeholder rx-post-nav__placeholder--%1$s" aria-hidden="true">
<svg class="rx-post-nav__placeholder-icon" width="48" height="48" viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true">
<path d="M4 5a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V5zm2 0v9.5l3.25-3.25a1 1 0 0 1 1.41 0L13 13.59l1.25-1.25a1 1 0 0 1 1.41 0L18 14.67V5H6zm0 14h12v-1.5l-3.05-3.05-1.25 1.25a1 1 0 0 1-1.41 0L9.95 13.36 6 17.31V19zM9 8.5A1.5 1.5 0 1 1 12 8.5 1.5 1.5 0 0 1 9 8.5z"></path>
</svg>
</span>',
esc_attr( $direction )
);
}
}
/**
* ------------------------------------------------------------
* Helper: Render one navigation item
* ------------------------------------------------------------
*/
if ( ! function_exists( 'rx_post_navigation_item' ) ) {
function rx_post_navigation_item( $post_object, $direction = 'next', $args = array() ) {
if ( ! $post_object instanceof WP_Post ) {
return '';
}
$post_id = absint( $post_object->ID );
$direction = 'previous' === $direction ? 'previous' : 'next';
$is_previous = 'previous' === $direction;
$url = get_permalink( $post_id );
$title = get_the_title( $post_id );
$title_attr = the_title_attribute(
array(
'post' => $post_id,
'echo' => false,
)
);
$taxonomy = ! empty( $args['taxonomy'] ) ? sanitize_key( $args['taxonomy'] ) : 'category';
$category = rx_post_navigation_get_primary_term( $post_id, $taxonomy );
$date = get_the_date( '', $post_id );
$author_id = absint( get_post_field( 'post_author', $post_id ) );
$author_name = $author_id ? get_the_author_meta( 'display_name', $author_id ) : '';
$reading_time = rx_post_navigation_get_reading_time( $post_id );
$word_count = rx_post_navigation_get_word_count( $post_id );
$comment_count = absint( get_comments_number( $post_id ) );
$excerpt_length = ! empty( $args['excerpt_length'] ) ? absint( $args['excerpt_length'] ) : 22;
$excerpt = rx_post_navigation_get_excerpt( $post_id, $excerpt_length );
$thumbnail_size = ! empty( $args['thumbnail_size'] ) ? $args['thumbnail_size'] : 'medium_large';
$fallback_image = ! empty( $args['fallback_image'] ) ? $args['fallback_image'] : '';
$direction_label = $is_previous ? __( 'Previous post', 'rx-theme' ) : __( 'Next post', 'rx-theme' );
$aria_label = sprintf(
/* translators: 1: navigation direction, 2: post title */
__( '%1$s: %2$s', 'rx-theme' ),
$direction_label,
$title
);
ob_start();
?>
<article class="rx-post-nav__item rx-post-nav__item--<?php echo esc_attr( $direction ); ?>" itemscope itemtype="https://schema.org/BlogPosting">
<a class="rx-post-nav__link rx-post-nav__link--<?php echo esc_attr( $direction ); ?>"
href="<?php echo esc_url( $url ); ?>"
rel="<?php echo $is_previous ? 'prev' : 'next'; ?>"
aria-label="<?php echo esc_attr( $aria_label ); ?>"
title="<?php echo esc_attr( $title_attr ); ?>"
itemprop="url">
<?php if ( ! empty( $args['show_thumbnail'] ) ) : ?>
<span class="rx-post-nav__media">
<?php
echo rx_post_navigation_thumbnail(
$post_id,
$thumbnail_size,
$fallback_image,
$direction
);
?>
</span>
<?php endif; ?>
<span class="rx-post-nav__content">
<span class="rx-post-nav__topline">
<?php if ( ! empty( $args['show_direction_icon'] ) ) : ?>
<span class="rx-post-nav__arrow rx-post-nav__arrow--<?php echo esc_attr( $direction ); ?>" aria-hidden="true">
<?php if ( $is_previous ) : ?>
<span class="rx-post-nav__arrow-symbol">←</span>
<?php else : ?>
<span class="rx-post-nav__arrow-symbol">→</span>
<?php endif; ?>
</span>
<?php endif; ?>
<span class="rx-post-nav__label">
<?php echo esc_html( $direction_label ); ?>
</span>
</span>
<span class="rx-post-nav__title" itemprop="headline">
<?php echo esc_html( $title ); ?>
</span>
<?php if ( ! empty( $args['show_excerpt'] ) && $excerpt ) : ?>
<span class="rx-post-nav__excerpt">
<?php echo esc_html( $excerpt ); ?>
</span>
<?php endif; ?>
<span class="rx-post-nav__meta">
<?php if ( ! empty( $args['show_category'] ) && $category ) : ?>
<span class="rx-post-nav__meta-item rx-post-nav__category">
<span class="screen-reader-text"><?php esc_html_e( 'Category:', 'rx-theme'); ?></span>
<?php echo esc_html( $category ); ?>
</span>
<?php endif; ?>
<?php if ( ! empty( $args['show_date'] ) && $date ) : ?>
<span class="rx-post-nav__meta-item rx-post-nav__date">
<time datetime="<?php echo esc_attr( get_the_date( DATE_W3C, $post_id ) ); ?>" itemprop="datePublished">
<?php echo esc_html( $date ); ?>
</time>
</span>
<?php endif; ?>
<?php if ( ! empty( $args['show_author'] ) && $author_name ) : ?>
<span class="rx-post-nav__meta-item rx-post-nav__author" itemprop="author" itemscope itemtype="https://schema.org/Person">
<span itemprop="name"><?php echo esc_html( $author_name ); ?></span>
</span>
<?php endif; ?>
<?php if ( ! empty( $args['show_reading_time'] ) && $reading_time ) : ?>
<span class="rx-post-nav__meta-item rx-post-nav__reading-time">
<?php echo esc_html( $reading_time ); ?>
</span>
<?php endif; ?>
<?php if ( ! empty( $args['show_word_count'] ) && $word_count ) : ?>
<span class="rx-post-nav__meta-item rx-post-nav__word-count">
<?php
printf(
/* translators: %s: word count */
esc_html__( '%s words', 'rx-theme' ),
esc_html( number_format_i18n( $word_count ) )
);
?>
</span>
<?php endif; ?>
<?php if ( ! empty( $args['show_comment_count'] ) ) : ?>
<span class="rx-post-nav__meta-item rx-post-nav__comments">
<?php
printf(
/* translators: %s: comment count */
esc_html( _n( '%s comment', '%s comments', $comment_count, 'rx-theme' ) ),
esc_html( number_format_i18n( $comment_count ) )
);
?>
</span>
<?php endif; ?>
</span>
</span>
</a>
</article>
<?php
return ob_get_clean();
}
}
$wrapper_class = ! empty( $rx_nav_args['wrapper_class'] ) ? sanitize_html_class( $rx_nav_args['wrapper_class'] ) : 'rx-post-navigation';
$nav_classes = array(
$wrapper_class,
'rx-post-navigation--advanced',
);
if ( $previous_post ) {
$nav_classes[] = 'has-previous-post';
}
if ( $next_post ) {
$nav_classes[] = 'has-next-post';
}
if ( ! $previous_post || ! $next_post ) {
$nav_classes[] = 'has-single-adjacent-post';
}
if ( $rx_same_term ) {
$nav_classes[] = 'is-same-term-enabled';
}
$nav_classes = array_map( 'sanitize_html_class', $nav_classes );
$nav_class = implode( ' ', array_filter( $nav_classes ) );
?>
<nav class="<?php echo esc_attr( $nav_class ); ?>"
aria-label="<?php echo esc_attr__( 'Post navigation', 'rx-theme' ); ?>"
data-rx-component="post-navigation">
<h2 class="screen-reader-text">
<?php esc_html_e( 'Post navigation', 'rx-theme' ); ?>
</h2>
<div class="rx-post-nav__inner">
<?php
if ( $previous_post ) {
echo rx_post_navigation_item( $previous_post, 'previous', $rx_nav_args );
} elseif ( ! empty( $rx_nav_args['show_empty_state'] ) ) {
?>
<div class="rx-post-nav__item rx-post-nav__item--previous rx-post-nav__item--empty">
<span class="rx-post-nav__empty-text">
<?php esc_html_e( 'No previous post found.', 'rx-theme' ); ?>
</span>
</div>
<?php
}
if ( $next_post ) {
echo rx_post_navigation_item( $next_post, 'next', $rx_nav_args );
} elseif ( ! empty( $rx_nav_args['show_empty_state'] ) ) {
?>
<div class="rx-post-nav__item rx-post-nav__item--next rx-post-nav__item--empty">
<span class="rx-post-nav__empty-text">
<?php esc_html_e( 'No next post found.', 'rx-theme' ); ?>
</span>
</div>
<?php
}
?>
</div>
</nav>
Add this CSS to your theme CSS file, for example:
assets/css/components/post-navigation.css
or directly in style.css:
/* ------------------------------------------------------------
RX Advanced Post Navigation
------------------------------------------------------------ */
.rx-post-navigation {
margin: 48px 0;
padding: 0;
clear: both;
}
.rx-post-navigation *,
.rx-post-navigation *::before,
.rx-post-navigation *::after {
box-sizing: border-box;
}
.rx-post-nav__inner {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px;
align-items: stretch;
}
.rx-post-navigation.has-single-adjacent-post .rx-post-nav__inner {
grid-template-columns: minmax(0, 1fr);
}
.rx-post-nav__item {
position: relative;
min-width: 0;
height: 100%;
border: 1px solid rgba(15, 23, 42, 0.12);
border-radius: 22px;
overflow: hidden;
background: #fff;
box-shadow: 0 12px 35px rgba(15, 23, 42, 0.08);
transition:
transform 0.25s ease,
box-shadow 0.25s ease,
border-color 0.25s ease;
}
.rx-post-nav__item:hover {
transform: translateY(-3px);
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.14);
border-color: rgba(37, 99, 235, 0.35);
}
.rx-post-nav__link {
display: grid;
grid-template-columns: 150px minmax(0, 1fr);
gap: 18px;
height: 100%;
min-height: 180px;
padding: 18px;
color: inherit;
text-decoration: none;
}
.rx-post-nav__link:hover,
.rx-post-nav__link:focus {
color: inherit;
text-decoration: none;
}
.rx-post-nav__link:focus-visible {
outline: 3px solid rgba(37, 99, 235, 0.45);
outline-offset: 4px;
border-radius: 18px;
}
.rx-post-nav__item--previous .rx-post-nav__link {
text-align: left;
}
.rx-post-nav__item--next .rx-post-nav__link {
text-align: right;
}
.rx-post-nav__item--next .rx-post-nav__media {
order: 2;
}
.rx-post-nav__item--next .rx-post-nav__content {
order: 1;
align-items: flex-end;
}
.rx-post-nav__media {
position: relative;
display: block;
width: 100%;
min-height: 140px;
border-radius: 16px;
overflow: hidden;
background: linear-gradient(135deg, rgba(37, 99, 235, 0.10), rgba(14, 165, 233, 0.08));
}
.rx-post-nav__image {
display: block;
width: 100%;
height: 100%;
min-height: 140px;
object-fit: cover;
transition: transform 0.3s ease;
}
.rx-post-nav__item:hover .rx-post-nav__image {
transform: scale(1.04);
}
.rx-post-nav__placeholder {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
min-height: 140px;
color: rgba(15, 23, 42, 0.35);
}
.rx-post-nav__placeholder-icon {
width: 52px;
height: 52px;
fill: currentColor;
}
.rx-post-nav__content {
display: flex;
flex-direction: column;
justify-content: center;
gap: 9px;
min-width: 0;
}
.rx-post-nav__topline {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 0.78rem;
font-weight: 800;
line-height: 1.2;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #2563eb;
}
.rx-post-nav__item--next .rx-post-nav__topline {
justify-content: flex-end;
}
.rx-post-nav__arrow {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 999px;
background: rgba(37, 99, 235, 0.10);
color: #2563eb;
font-size: 1rem;
line-height: 1;
transition:
background-color 0.25s ease,
color 0.25s ease,
transform 0.25s ease;
}
.rx-post-nav__item:hover .rx-post-nav__arrow {
background: #2563eb;
color: #fff;
}
.rx-post-nav__item--previous:hover .rx-post-nav__arrow {
transform: translateX(-3px);
}
.rx-post-nav__item--next:hover .rx-post-nav__arrow {
transform: translateX(3px);
}
.rx-post-nav__title {
display: block;
font-size: clamp(1.05rem, 2vw, 1.35rem);
font-weight: 900;
line-height: 1.25;
color: #0f172a;
overflow-wrap: anywhere;
}
.rx-post-nav__excerpt {
display: block;
font-size: 0.94rem;
line-height: 1.6;
color: #475569;
}
.rx-post-nav__meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
font-size: 0.78rem;
line-height: 1.3;
color: #64748b;
}
.rx-post-nav__item--next .rx-post-nav__meta {
justify-content: flex-end;
}
.rx-post-nav__meta-item {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 9px;
border-radius: 999px;
background: rgba(15, 23, 42, 0.055);
white-space: nowrap;
}
.rx-post-nav__category {
background: rgba(37, 99, 235, 0.10);
color: #1d4ed8;
font-weight: 800;
}
.rx-post-nav__item--empty {
display: flex;
align-items: center;
justify-content: center;
min-height: 150px;
padding: 24px;
color: #64748b;
background: rgba(248, 250, 252, 0.8);
box-shadow: none;
}
.rx-post-nav__empty-text {
font-size: 0.95rem;
font-weight: 700;
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.rx-post-nav__item {
background: #0f172a;
border-color: rgba(255, 255, 255, 0.10);
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.28);
}
.rx-post-nav__title {
color: #f8fafc;
}
.rx-post-nav__excerpt {
color: #cbd5e1;
}
.rx-post-nav__meta {
color: #94a3b8;
}
.rx-post-nav__meta-item {
background: rgba(255, 255, 255, 0.08);
}
.rx-post-nav__category {
background: rgba(59, 130, 246, 0.20);
color: #93c5fd;
}
}
/* Tablet */
@media (max-width: 900px) {
.rx-post-nav__inner {
grid-template-columns: 1fr;
}
.rx-post-nav__item--next .rx-post-nav__link {
text-align: left;
}
.rx-post-nav__item--next .rx-post-nav__media {
order: 1;
}
.rx-post-nav__item--next .rx-post-nav__content {
order: 2;
align-items: flex-start;
}
.rx-post-nav__item--next .rx-post-nav__topline,
.rx-post-nav__item--next .rx-post-nav__meta {
justify-content: flex-start;
}
}
/* Mobile */
@media (max-width: 600px) {
.rx-post-navigation {
margin: 36px 0;
}
.rx-post-nav__link {
grid-template-columns: 1fr;
min-height: auto;
padding: 16px;
}
.rx-post-nav__media,
.rx-post-nav__image,
.rx-post-nav__placeholder {
min-height: 190px;
}
.rx-post-nav__title {
font-size: 1.1rem;
}
.rx-post-nav__excerpt {
font-size: 0.9rem;
}
.rx-post-nav__meta {
gap: 6px;
}
.rx-post-nav__meta-item {
white-space: normal;
}
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.rx-post-nav__item,
.rx-post-nav__image,
.rx-post-nav__arrow {
transition: none;
}
.rx-post-nav__item:hover {
transform: none;
}
.rx-post-nav__item:hover .rx-post-nav__image {
transform: none;
}
}
Call it inside your single.php after post content:
<?php get_template_part( 'template-parts/post/post-navigation' ); ?>
Optional customization from functions.php:
/**
* RX Theme: Customize advanced post navigation.
*/
add_filter( 'rx_post_navigation_args', function( $args ) {
$args['same_term'] = true;
$args['taxonomy'] = 'category';
$args['show_thumbnail'] = true;
$args['show_category'] = true;
$args['show_date'] = true;
$args['show_author'] = true;
$args['show_excerpt'] = true;
$args['show_reading_time'] = true;
$args['show_word_count'] = true;
$args['show_comment_count'] = true;
$args['thumbnail_size'] = 'medium_large';
$args['excerpt_length'] = 24;
// Example fallback image:
// $args['fallback_image'] = get_template_directory_uri() . '/assets/images/fallback-post.jpg';
return $args;
} );
This file is strong for your RX theme because it does not only show “Previous / Next”; it creates a professional post-navigation card system with SEO-friendly structure, accessibility, safe escaping, same-category intelligence, image fallback, and future filter support.