related-posts.php

I’ll build this as a powerful drop-in template-parts/post/related-posts.php file for your RX Theme, using safe WordPress functions, caching, taxonomy matching, author/date fallback, thumbnails, schema, accessibility, and responsive markup.

Below is an advanced, long, feature-rich template-parts/post/related-posts.php file for your RX Theme.

It includes: category/tag/custom taxonomy matching, fallback posts, popular/commented/random fallback, transient cache, accessibility, schema JSON-LD, thumbnail fallback, reading time, post meta, excerpt trimming, responsive class names, safe escaping, filters for future expansion, and proper wp_reset_postdata() after custom WP_Query. WordPress officially recommends wp_reset_postdata() after secondary loops, and get_the_terms() is the correct function for retrieving post taxonomy terms.

Create this file:

rx-theme/template-parts/post/related-posts.php

Then paste this full code:

<?php
/**
 * Template Part: Related Posts
 *
 * File path:
 * template-parts/post/related-posts.php
 *
 * RX Theme Advanced Related Posts System
 *
 * Features:
 * - Related posts by category, tag, and custom taxonomies
 * - Fallback query if no related posts found
 * - Optional popular posts fallback by comments
 * - Optional random fallback
 * - Transient caching for performance
 * - Thumbnail fallback support
 * - Reading time support
 * - Excerpt support
 * - Author/date/category/comment meta
 * - Schema JSON-LD ItemList
 * - Accessibility-friendly markup
 * - Developer filters for future expansion
 *
 * Usage in single.php:
 *
 * get_template_part( 'template-parts/post/related-posts' );
 *
 * @package RX_Theme
 */

defined( 'ABSPATH' ) || exit;

if ( ! is_singular( 'post' ) ) {
	return;
}

$current_post_id = get_the_ID();

if ( ! $current_post_id ) {
	return;
}

/**
 * ------------------------------------------------------------
 * 1. Basic Settings
 * ------------------------------------------------------------
 */

$rx_related_defaults = array(
	'enabled'                => true,
	'title'                  => __( 'Related Articles', 'rx-theme' ),
	'subtitle'               => __( 'You may also like these carefully selected posts.', 'rx-theme' ),
	'posts_per_page'         => 6,
	'columns'                => 3,
	'excerpt_length'         => 22,
	'show_thumbnail'         => true,
	'show_category'          => true,
	'show_date'              => true,
	'show_author'            => true,
	'show_comments'          => true,
	'show_excerpt'           => true,
	'show_reading_time'      => true,
	'show_badge'             => true,
	'show_schema'            => true,
	'cache_enabled'          => true,
	'cache_expiration'       => 12 * HOUR_IN_SECONDS,
	'fallback_enabled'       => true,
	'fallback_orderby'       => 'date',
	'random_fallback'        => false,
	'popular_fallback'       => true,
	'thumbnail_size'         => 'medium_large',
	'default_thumbnail_url'  => '',
	'section_id'             => 'rx-related-posts',
	'card_style'             => 'default',
	'open_links_new_tab'     => false,
	'include_sticky_posts'   => false,
);

/**
 * Filter: rx_related_posts_settings
 *
 * Example:
 * add_filter( 'rx_related_posts_settings', function( $settings ) {
 *     $settings['posts_per_page'] = 8;
 *     $settings['columns'] = 4;
 *     return $settings;
 * });
 */
$rx_related_settings = apply_filters(
	'rx_related_posts_settings',
	$rx_related_defaults,
	$current_post_id
);

if ( empty( $rx_related_settings['enabled'] ) ) {
	return;
}

$rx_posts_per_page = absint( $rx_related_settings['posts_per_page'] );

if ( $rx_posts_per_page < 1 ) {
	$rx_posts_per_page = 6;
}

$rx_columns = absint( $rx_related_settings['columns'] );

if ( $rx_columns < 1 || $rx_columns > 6 ) {
	$rx_columns = 3;
}

/**
 * ------------------------------------------------------------
 * 2. Helper Functions
 * ------------------------------------------------------------
 */

if ( ! function_exists( 'rx_related_posts_get_term_ids' ) ) {
	/**
	 * Get term IDs from a post taxonomy.
	 *
	 * @param int    $post_id  Post ID.
	 * @param string $taxonomy Taxonomy name.
	 * @return array
	 */
	function rx_related_posts_get_term_ids( $post_id, $taxonomy ) {
		$post_id  = absint( $post_id );
		$taxonomy = sanitize_key( $taxonomy );

		if ( ! $post_id || ! taxonomy_exists( $taxonomy ) ) {
			return array();
		}

		$terms = get_the_terms( $post_id, $taxonomy );

		if ( empty( $terms ) || is_wp_error( $terms ) ) {
			return array();
		}

		$term_ids = wp_list_pluck( $terms, 'term_id' );
		$term_ids = array_map( 'absint', $term_ids );
		$term_ids = array_filter( $term_ids );

		return array_values( array_unique( $term_ids ) );
	}
}

if ( ! function_exists( 'rx_related_posts_trim_excerpt' ) ) {
	/**
	 * Create safe trimmed excerpt.
	 *
	 * @param int $post_id Post ID.
	 * @param int $length  Word length.
	 * @return string
	 */
	function rx_related_posts_trim_excerpt( $post_id, $length = 22 ) {
		$post_id = absint( $post_id );
		$length  = absint( $length );

		if ( ! $post_id ) {
			return '';
		}

		if ( $length < 5 ) {
			$length = 22;
		}

		$manual_excerpt = get_the_excerpt( $post_id );

		if ( ! empty( $manual_excerpt ) ) {
			return wp_trim_words( wp_strip_all_tags( $manual_excerpt ), $length, '&hellip;' );
		}

		$content = get_post_field( 'post_content', $post_id );
		$content = strip_shortcodes( $content );
		$content = wp_strip_all_tags( $content );

		return wp_trim_words( $content, $length, '&hellip;' );
	}
}

if ( ! function_exists( 'rx_related_posts_reading_time' ) ) {
	/**
	 * Calculate estimated reading time.
	 *
	 * @param int $post_id Post ID.
	 * @return string
	 */
	function rx_related_posts_reading_time( $post_id ) {
		$post_id = absint( $post_id );

		if ( ! $post_id ) {
			return '';
		}

		$content = get_post_field( 'post_content', $post_id );
		$content = wp_strip_all_tags( strip_shortcodes( $content ) );

		$word_count = str_word_count( $content );
		$minutes    = max( 1, ceil( $word_count / 200 ) );

		return sprintf(
			/* translators: %s: reading time minutes */
			_n( '%s min read', '%s mins read', $minutes, 'rx-theme' ),
			number_format_i18n( $minutes )
		 );
	}
}

if ( ! function_exists( 'rx_related_posts_primary_category' ) ) {
	/**
	 * Get first category for display.
	 *
	 * @param int $post_id Post ID.
	 * @return WP_Term|null
	 */
	function rx_related_posts_primary_category( $post_id ) {
		$post_id = absint( $post_id );

		if ( ! $post_id ) {
			return null;
		}

		$categories = get_the_category( $post_id );

		if ( empty( $categories ) || is_wp_error( $categories ) ) {
			return null;
		}

		return $categories[0];
	}
}

if ( ! function_exists( 'rx_related_posts_thumbnail' ) ) {
	/**
	 * Render related post thumbnail.
	 *
	 * @param int    $post_id               Post ID.
	 * @param string $size                  Image size.
	 * @param string $default_thumbnail_url Default image URL.
	 * @return string
	 */
	function rx_related_posts_thumbnail( $post_id, $size = 'medium_large', $default_thumbnail_url = '' ) {
		$post_id = absint( $post_id );
		$size    = sanitize_key( $size );

		if ( ! $post_id ) {
			return '';
		}

		if ( has_post_thumbnail( $post_id ) ) {
			return get_the_post_thumbnail(
				$post_id,
				$size,
				array(
					'class'   => 'rx-related-posts__image',
					'loading' => 'lazy',
					'alt'     => esc_attr( get_the_title( $post_id ) ),
				)
			);
		}

		if ( ! empty( $default_thumbnail_url ) ) {
			return sprintf(
				'<img class="rx-related-posts__image rx-related-posts__image--fallback" src="%1$s" alt="%2$s" loading="lazy" />',
				esc_url( $default_thumbnail_url ),
				esc_attr( get_the_title( $post_id ) )
			);
		}

		return sprintf(
			'<div class="rx-related-posts__image-placeholder" aria-hidden="true">
				<span class="rx-related-posts__image-placeholder-icon">RX</span>
			</div>'
		);
	}
}

if ( ! function_exists( 'rx_related_posts_cache_key' ) ) {
	/**
	 * Build transient cache key.
	 *
	 * @param int   $post_id  Post ID.
	 * @param array $settings Settings.
	 * @return string
	 */
	function rx_related_posts_cache_key( $post_id, $settings ) {
		$post_id = absint( $post_id );

		$key_data = array(
			'post_id'        => $post_id,
			'posts_per_page' => isset( $settings['posts_per_page'] ) ? absint( $settings['posts_per_page'] ) : 6,
			'columns'        => isset( $settings['columns'] ) ? absint( $settings['columns'] ) : 3,
			'fallback'       => ! empty( $settings['fallback_enabled'] ),
			'popular'        => ! empty( $settings['popular_fallback'] ),
			'random'         => ! empty( $settings['random_fallback'] ),
		);

		return 'rx_related_posts_' . md5( wp_json_encode( $key_data ) );
	}
}

/**
 * ------------------------------------------------------------
 * 3. Build Taxonomy Query
 * ------------------------------------------------------------
 */

$rx_category_ids = rx_related_posts_get_term_ids( $current_post_id, 'category' );
$rx_tag_ids      = rx_related_posts_get_term_ids( $current_post_id, 'post_tag' );

/**
 * Add custom taxonomies if your theme uses them.
 *
 * Example custom taxonomies:
 * - disease_category
 * - topic
 * - medical_specialty
 * - rx_condition
 */
$rx_custom_taxonomies = apply_filters(
	'rx_related_posts_custom_taxonomies',
	array(),
	$current_post_id
);

$rx_tax_query = array(
	'relation' => 'OR',
);

if ( ! empty( $rx_category_ids ) ) {
	$rx_tax_query[] = array(
		'taxonomy'         => 'category',
		'field'            => 'term_id',
		'terms'            => $rx_category_ids,
		'include_children' => true,
		'operator'         => 'IN',
	);
}

if ( ! empty( $rx_tag_ids ) ) {
	$rx_tax_query[] = array(
		'taxonomy'         => 'post_tag',
		'field'            => 'term_id',
		'terms'            => $rx_tag_ids,
		'include_children' => false,
		'operator'         => 'IN',
	);
}

if ( ! empty( $rx_custom_taxonomies ) && is_array( $rx_custom_taxonomies ) ) {
	foreach ( $rx_custom_taxonomies as $rx_taxonomy ) {
		$rx_taxonomy = sanitize_key( $rx_taxonomy );

		if ( ! taxonomy_exists( $rx_taxonomy ) ) {
			continue;
		}

		$rx_term_ids = rx_related_posts_get_term_ids( $current_post_id, $rx_taxonomy );

		if ( empty( $rx_term_ids ) ) {
			continue;
		}

		$rx_tax_query[] = array(
			'taxonomy'         => $rx_taxonomy,
			'field'            => 'term_id',
			'terms'            => $rx_term_ids,
			'include_children' => true,
			'operator'         => 'IN',
		);
	}
}

$rx_has_tax_query = count( $rx_tax_query ) > 1;

/**
 * ------------------------------------------------------------
 * 4. Query Related Posts
 * ------------------------------------------------------------
 */

$rx_cache_key     = rx_related_posts_cache_key( $current_post_id, $rx_related_settings );
$rx_related_posts = false;

if ( ! empty( $rx_related_settings['cache_enabled'] ) ) {
	$rx_related_posts = get_transient( $rx_cache_key );
}

if ( false === $rx_related_posts ) {
	$rx_related_posts = array();

	$rx_base_args = array(
		'post_type'              => 'post',
		'post_status'            => 'publish',
		'posts_per_page'         => $rx_posts_per_page,
		'post__not_in'           => array( $current_post_id ),
		'ignore_sticky_posts'    => empty( $rx_related_settings['include_sticky_posts'] ),
		'no_found_rows'          => true,
		'update_post_meta_cache' => true,
		'update_post_term_cache' => true,
	);

	if ( $rx_has_tax_query ) {
		$rx_related_args = array_merge(
			$rx_base_args,
			array(
				'tax_query' => $rx_tax_query,
				'orderby'   => array(
					'date'          => 'DESC',
					'comment_count' => 'DESC',
				),
			)
		);

		/**
		 * Filter: rx_related_posts_query_args
		 */
		$rx_related_args = apply_filters(
			'rx_related_posts_query_args',
			$rx_related_args,
			$current_post_id,
			$rx_related_settings
		);

		$rx_related_query = new WP_Query( $rx_related_args );

		if ( $rx_related_query->have_posts() ) {
			foreach ( $rx_related_query->posts as $rx_post_object ) {
				$rx_related_posts[] = absint( $rx_post_object->ID );
			}
		}

		wp_reset_postdata();
	}

	/**
	 * ------------------------------------------------------------
	 * 5. Fallback Query
	 * ------------------------------------------------------------
	 */

	if (
		! empty( $rx_related_settings['fallback_enabled'] )
		&& count( $rx_related_posts ) < $rx_posts_per_page
	) {
		$rx_needed_posts = $rx_posts_per_page - count( $rx_related_posts );

		$rx_exclude_posts = array_merge(
			array( $current_post_id ),
			$rx_related_posts
		);

		$rx_fallback_orderby = sanitize_key( $rx_related_settings['fallback_orderby'] );

		if ( ! empty( $rx_related_settings['random_fallback'] ) ) {
			$rx_fallback_orderby = 'rand';
		}

		$rx_fallback_args = array_merge(
			$rx_base_args,
			array(
				'posts_per_page' => $rx_needed_posts,
				'post__not_in'   => array_map( 'absint', $rx_exclude_posts ),
				'orderby'        => $rx_fallback_orderby,
				'order'          => 'DESC',
			)
		);

		if ( ! empty( $rx_related_settings['popular_fallback'] ) ) {
			$rx_fallback_args['orderby'] = 'comment_count';
		}

		/**
		 * Filter: rx_related_posts_fallback_query_args
		 */
		$rx_fallback_args = apply_filters(
			'rx_related_posts_fallback_query_args',
			$rx_fallback_args,
			$current_post_id,
			$rx_related_posts,
			$rx_related_settings
		);

		$rx_fallback_query = new WP_Query( $rx_fallback_args );

		if ( $rx_fallback_query->have_posts() ) {
			foreach ( $rx_fallback_query->posts as $rx_post_object ) {
				$rx_related_posts[] = absint( $rx_post_object->ID );
			}
		}

		wp_reset_postdata();
	}

	$rx_related_posts = array_values( array_unique( array_filter( array_map( 'absint', $rx_related_posts ) ) ) );

	if ( ! empty( $rx_related_settings['cache_enabled'] ) ) {
		set_transient(
			$rx_cache_key,
			$rx_related_posts,
			absint( $rx_related_settings['cache_expiration'] )
		);
	}
}

/**
 * Final filter before rendering.
 */
$rx_related_posts = apply_filters(
	'rx_related_posts_ids',
	$rx_related_posts,
	$current_post_id,
	$rx_related_settings
);

if ( empty( $rx_related_posts ) || ! is_array( $rx_related_posts ) ) {
	return;
}

/**
 * ------------------------------------------------------------
 * 6. Markup Settings
 * ------------------------------------------------------------
 */

$rx_section_id = ! empty( $rx_related_settings['section_id'] )
	? sanitize_html_class( $rx_related_settings['section_id'] )
	: 'rx-related-posts';

$rx_card_style = ! empty( $rx_related_settings['card_style'] )
	? sanitize_html_class( $rx_related_settings['card_style'] )
	: 'default';

$rx_target = ! empty( $rx_related_settings['open_links_new_tab'] ) ? ' target="_blank"' : '';
$rx_rel    = ! empty( $rx_related_settings['open_links_new_tab'] ) ? ' rel="noopener noreferrer"' : '';

$rx_section_classes = array(
	'rx-related-posts',
	'rx-related-posts--columns-' . $rx_columns,
	'rx-related-posts--style-' . $rx_card_style,
);

$rx_section_classes = apply_filters(
	'rx_related_posts_section_classes',
	$rx_section_classes,
	$current_post_id,
	$rx_related_settings
);

$rx_section_class = implode( ' ', array_map( 'sanitize_html_class', $rx_section_classes ) );

/**
 * ------------------------------------------------------------
 * 7. Schema Data
 * ------------------------------------------------------------
 */

$rx_schema_items = array();

if ( ! empty( $rx_related_settings['show_schema'] ) ) {
	$rx_schema_position = 1;

	foreach ( $rx_related_posts as $rx_related_post_id ) {
		$rx_schema_items[] = array(
			'@type'    => 'ListItem',
			'position' => $rx_schema_position,
			'url'      => get_permalink( $rx_related_post_id ),
			'name'     => wp_strip_all_tags( get_the_title( $rx_related_post_id ) ),
		);

		$rx_schema_position++;
	}
}

?>

<section
	id="<?php echo esc_attr( $rx_section_id ); ?>"
	class="<?php echo esc_attr( $rx_section_class ); ?>"
	aria-labelledby="<?php echo esc_attr( $rx_section_id ); ?>-title"
>
	<div class="rx-related-posts__inner">

		<header class="rx-related-posts__header">
			<?php if ( ! empty( $rx_related_settings['title'] ) ) : ?>
				<h2 id="<?php echo esc_attr( $rx_section_id ); ?>-title" class="rx-related-posts__title">
					<?php echo esc_html( $rx_related_settings['title'] ); ?>
				</h2>
			<?php endif; ?>

			<?php if ( ! empty( $rx_related_settings['subtitle'] ) ) : ?>
				<p class="rx-related-posts__subtitle">
					<?php echo esc_html( $rx_related_settings['subtitle'] ); ?>
				</p>
			<?php endif; ?>
		</header>

		<div class="rx-related-posts__grid">
			<?php foreach ( $rx_related_posts as $rx_related_post_id ) : ?>
				<?php
				$rx_related_post_id = absint( $rx_related_post_id );

				if ( ! $rx_related_post_id ) {
					continue;
				}

				$rx_title     = get_the_title( $rx_related_post_id );
				$rx_permalink = get_permalink( $rx_related_post_id );
				$rx_category  = rx_related_posts_primary_category( $rx_related_post_id );
				$rx_excerpt   = rx_related_posts_trim_excerpt(
					$rx_related_post_id,
					absint( $rx_related_settings['excerpt_length'] )
				);
				$rx_author_id = absint( get_post_field( 'post_author', $rx_related_post_id ) );
				?>

				<article
					class="rx-related-posts__card"
					itemscope
					itemtype="https://schema.org/BlogPosting"
				>
					<?php if ( ! empty( $rx_related_settings['show_thumbnail'] ) ) : ?>
						<a
							class="rx-related-posts__media"
							href="<?php echo esc_url( $rx_permalink ); ?>"
							aria-label="<?php echo esc_attr( sprintf( __( 'Read article: %s', 'rx-theme' ), $rx_title ) ); ?>"
							<?php echo $rx_target; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
							<?php echo $rx_rel; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
						>
							<?php
							echo rx_related_posts_thumbnail(
								$rx_related_post_id,
								$rx_related_settings['thumbnail_size'],
								$rx_related_settings['default_thumbnail_url']
							); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
							?>

							<?php if ( ! empty( $rx_related_settings['show_badge'] ) && ! empty( $rx_category ) ) : ?>
								<span class="rx-related-posts__badge">
									<?php echo esc_html( $rx_category->name ); ?>
								</span>
							<?php endif; ?>
						</a>
					<?php endif; ?>

					<div class="rx-related-posts__content">

						<?php if ( ! empty( $rx_related_settings['show_category'] ) && ! empty( $rx_category ) ) : ?>
							<div class="rx-related-posts__category">
								<a href="<?php echo esc_url( get_category_link( $rx_category->term_id ) ); ?>">
									<?php echo esc_html( $rx_category->name ); ?>
								</a>
							</div>
						<?php endif; ?>

						<h3 class="rx-related-posts__post-title" itemprop="headline">
							<a
								href="<?php echo esc_url( $rx_permalink ); ?>"
								itemprop="url"
								<?php echo $rx_target; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
								<?php echo $rx_rel; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
							>
								<?php echo esc_html( $rx_title ); ?>
							</a>
						</h3>

						<div class="rx-related-posts__meta">

							<?php if ( ! empty( $rx_related_settings['show_author'] ) && $rx_author_id ) : ?>
								<span class="rx-related-posts__meta-item rx-related-posts__meta-author" itemprop="author" itemscope itemtype="https://schema.org/Person">
									<span class="rx-related-posts__meta-label">
										<?php esc_html_e( 'By', 'rx-theme' ); ?>
									</span>
									<a href="<?php echo esc_url( get_author_posts_url( $rx_author_id ) ); ?>" itemprop="url">
										<span itemprop="name">
											<?php echo esc_html( get_the_author_meta( 'display_name', $rx_author_id ) ); ?>
										</span>
									</a>
								</span>
							<?php endif; ?>

							<?php if ( ! empty( $rx_related_settings['show_date'] ) ) : ?>
								<span class="rx-related-posts__meta-item rx-related-posts__meta-date">
									<time
										datetime="<?php echo esc_attr( get_the_date( DATE_W3C, $rx_related_post_id ) ); ?>"
										itemprop="datePublished"
									>
										<?php echo esc_html( get_the_date( '', $rx_related_post_id ) ); ?>
									</time>
								</span>
							<?php endif; ?>

							<?php if ( ! empty( $rx_related_settings['show_reading_time'] ) ) : ?>
								<span class="rx-related-posts__meta-item rx-related-posts__meta-reading-time">
									<?php echo esc_html( rx_related_posts_reading_time( $rx_related_post_id ) ); ?>
								</span>
							<?php endif; ?>

							<?php if ( ! empty( $rx_related_settings['show_comments'] ) ) : ?>
								<span class="rx-related-posts__meta-item rx-related-posts__meta-comments">
									<?php
									printf(
										esc_html(
											_n(
												'%s comment',
												'%s comments',
												get_comments_number( $rx_related_post_id ),
												'rx-theme'
											)
										),
										esc_html( number_format_i18n( get_comments_number( $rx_related_post_id ) ) )
									);
									?>
								</span>
							<?php endif; ?>

						</div>

						<?php if ( ! empty( $rx_related_settings['show_excerpt'] ) && ! empty( $rx_excerpt ) ) : ?>
							<p class="rx-related-posts__excerpt" itemprop="description">
								<?php echo esc_html( $rx_excerpt ); ?>
							</p>
						<?php endif; ?>

						<a
							class="rx-related-posts__read-more"
							href="<?php echo esc_url( $rx_permalink ); ?>"
							aria-label="<?php echo esc_attr( sprintf( __( 'Continue reading %s', 'rx-theme' ), $rx_title ) ); ?>"
							<?php echo $rx_target; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
							<?php echo $rx_rel; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
						>
							<?php esc_html_e( 'Read More', 'rx-theme' ); ?>
							<span aria-hidden="true"></span>
						</a>

					</div>

					<meta itemprop="mainEntityOfPage" content="<?php echo esc_url( $rx_permalink ); ?>">
					<meta itemprop="dateModified" content="<?php echo esc_attr( get_the_modified_date( DATE_W3C, $rx_related_post_id ) ); ?>">

				</article>
			<?php endforeach; ?>
		</div>
	</div>

	<?php if ( ! empty( $rx_related_settings['show_schema'] ) && ! empty( $rx_schema_items ) ) : ?>
		<script type="application/ld+json">
			<?php
			echo wp_json_encode(
				array(
					'@context'        => 'https://schema.org',
					'@type'           => 'ItemList',
					'name'            => wp_strip_all_tags( $rx_related_settings['title'] ),
					'itemListElement' => $rx_schema_items,
				),
				JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT
			);
			?>
		</script>
	<?php endif; ?>

</section>

Add this in single.php

Place it after post content, before comments:

<?php
get_template_part( 'template-parts/post/related-posts' );
?>

Optional CSS for style.css

.rx-related-posts {
	margin-top: 48px;
	padding-top: 36px;
	border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.rx-related-posts__header {
	margin-bottom: 24px;
	text-align: center;
}

.rx-related-posts__title {
	margin: 0 0 8px;
	font-size: clamp(1.5rem, 2vw, 2rem);
	line-height: 1.25;
}

.rx-related-posts__subtitle {
	margin: 0 auto;
	max-width: 680px;
	color: #666;
	font-size: 1rem;
	line-height: 1.7;
}

.rx-related-posts__grid {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 24px;
}

.rx-related-posts--columns-1 .rx-related-posts__grid {
	grid-template-columns: 1fr;
}

.rx-related-posts--columns-2 .rx-related-posts__grid {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.rx-related-posts--columns-3 .rx-related-posts__grid {
	grid-template-columns: repeat(3, minmax(0, 1fr));
}

.rx-related-posts--columns-4 .rx-related-posts__grid {
	grid-template-columns: repeat(4, minmax(0, 1fr));
}

.rx-related-posts__card {
	overflow: hidden;
	border: 1px solid rgba(0, 0, 0, 0.08);
	border-radius: 18px;
	background: #fff;
	box-shadow: 0 8px 28px rgba(0, 0, 0, 0.06);
	transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.rx-related-posts__card:hover {
	transform: translateY(-4px);
	box-shadow: 0 14px 36px rgba(0, 0, 0, 0.1);
}

.rx-related-posts__media {
	position: relative;
	display: block;
	overflow: hidden;
	aspect-ratio: 16 / 9;
	background: #f4f4f4;
	text-decoration: none;
}

.rx-related-posts__image {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	transition: transform 0.3s ease;
}

.rx-related-posts__card:hover .rx-related-posts__image {
	transform: scale(1.05);
}

.rx-related-posts__image-placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 100%;
	background: linear-gradient(135deg, #f3f4f6, #e5e7eb);
}

.rx-related-posts__image-placeholder-icon {
	font-weight: 800;
	font-size: 2rem;
	letter-spacing: 0.08em;
	color: #777;
}

.rx-related-posts__badge {
	position: absolute;
	left: 14px;
	top: 14px;
	z-index: 2;
	padding: 6px 10px;
	border-radius: 999px;
	background: rgba(0, 0, 0, 0.72);
	color: #fff;
	font-size: 0.75rem;
	font-weight: 700;
	line-height: 1;
}

.rx-related-posts__content {
	padding: 18px;
}

.rx-related-posts__category {
	margin-bottom: 8px;
	font-size: 0.78rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.rx-related-posts__category a {
	text-decoration: none;
}

.rx-related-posts__post-title {
	margin: 0 0 10px;
	font-size: 1.1rem;
	line-height: 1.35;
}

.rx-related-posts__post-title a {
	color: inherit;
	text-decoration: none;
}

.rx-related-posts__post-title a:hover {
	text-decoration: underline;
}

.rx-related-posts__meta {
	display: flex;
	flex-wrap: wrap;
	gap: 8px 12px;
	margin-bottom: 12px;
	color: #666;
	font-size: 0.82rem;
	line-height: 1.5;
}

.rx-related-posts__meta a {
	color: inherit;
	text-decoration: none;
}

.rx-related-posts__meta a:hover {
	text-decoration: underline;
}

.rx-related-posts__excerpt {
	margin: 0 0 14px;
	color: #555;
	font-size: 0.95rem;
	line-height: 1.7;
}

.rx-related-posts__read-more {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-weight: 700;
	text-decoration: none;
}

.rx-related-posts__read-more:hover {
	text-decoration: underline;
}

@media (max-width: 1024px) {
	.rx-related-posts__grid,
	.rx-related-posts--columns-3 .rx-related-posts__grid,
	.rx-related-posts--columns-4 .rx-related-posts__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (max-width: 640px) {
	.rx-related-posts {
		margin-top: 36px;
		padding-top: 28px;
	}

	.rx-related-posts__grid,
	.rx-related-posts--columns-2 .rx-related-posts__grid,
	.rx-related-posts--columns-3 .rx-related-posts__grid,
	.rx-related-posts--columns-4 .rx-related-posts__grid {
		grid-template-columns: 1fr;
	}
}

Optional: control it from functions.php

Add this if you want to customize the related posts globally:

add_filter( 'rx_related_posts_settings', function( $settings ) {
	$settings['title']                 = 'Related Medical Articles';
	$settings['subtitle']              = 'More useful articles selected from similar topics.';
	$settings['posts_per_page']        = 6;
	$settings['columns']               = 3;
	$settings['show_thumbnail']        = true;
	$settings['show_excerpt']          = true;
	$settings['show_author']           = true;
	$settings['show_date']             = true;
	$settings['show_comments']         = false;
	$settings['show_reading_time']     = true;
	$settings['cache_enabled']         = true;
	$settings['cache_expiration']      = 12 * HOUR_IN_SECONDS;
	$settings['popular_fallback']      = true;
	$settings['random_fallback']       = false;
	$settings['default_thumbnail_url'] = get_template_directory_uri() . '/assets/images/default-related.jpg';

	return $settings;
} );

Optional: include custom taxonomies

If your RX Theme later has medical custom taxonomies, add:

add_filter( 'rx_related_posts_custom_taxonomies', function( $taxonomies ) {
	$taxonomies[] = 'disease_category';
	$taxonomies[] = 'medical_specialty';
	$taxonomies[] = 'rx_condition';
	$taxonomies[] = 'topic';

	return $taxonomies;
} );

This file is safe for a professional WordPress theme because it uses escaping, avoids heavy pagination queries with no_found_rows, supports caching, excludes the current post, and resets post data after custom queries.

Leave a Reply

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