related-posts.php

I’ll build this as a strong related-posts.php template part for your RX theme: secure WordPress coding, multiple matching methods, fallbacks, schema-friendly markup, responsive layout hooks, caching, and many filters so you can expand it later.

Below is an advanced related-posts.php file for your RX theme. Put it here:

rx-theme/
└── template-parts/
    └── related-posts.php

Then call it inside single.php after post content:

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

This version uses WordPress-safe escaping, WP_Query, taxonomies, tags, categories, fallback posts, transients cache, thumbnail support, reading time, post views support, comments count, author display, schema-style attributes, accessibility, and many filters so you can expand later. WordPress officially provides functions like wp_get_post_terms() for post terms, and recommends escaping output with functions such as esc_url(), esc_html(), and wp_kses_post().

<?php
/**
 * RX Theme Advanced Related Posts Template
 *
 * File: template-parts/related-posts.php
 *
 * Features:
 * - Related posts by category, tag, and custom taxonomy.
 * - Fallback to recent/popular/random posts.
 * - Transient caching.
 * - Thumbnail, author, date, comments, excerpt, reading time.
 * - Accessibility-ready markup.
 * - SEO-friendly article structure.
 * - Filter hooks for future customization.
 * - Safe escaping and theme-friendly class names.
 *
 * Usage in single.php:
 * get_template_part( 'template-parts/related-posts' );
 *
 * @package RX_Theme
 */

defined( 'ABSPATH' ) || exit;

if ( ! is_singular() ) {
	return;
}

global $post;

if ( ! $post instanceof WP_Post ) {
	return;
}

$current_post_id = absint( get_the_ID() );

if ( ! $current_post_id ) {
	return;
}

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

$rx_related_defaults = array(
	'enabled'                 => true,
	'title'                   => __( 'Related Posts', 'rx-theme' ),
	'subtitle'                => __( 'You may also like these articles', 'rx-theme' ),
	'post_types'              => array( 'post' ),
	'posts_per_page'          => 6,
	'columns'                 => 3,
	'orderby'                 => 'date',
	'order'                   => 'DESC',
	'match_categories'        => true,
	'match_tags'              => true,
	'match_custom_taxonomies' => true,
	'custom_taxonomies'       => array(),
	'exclude_current_post'    => true,
	'exclude_sticky_posts'    => true,
	'show_thumbnail'          => true,
	'show_category'           => true,
	'show_date'               => true,
	'show_author'             => true,
	'show_comments'           => true,
	'show_excerpt'            => true,
	'show_reading_time'       => true,
	'show_post_format'        => false,
	'show_views'              => true,
	'excerpt_length'          => 18,
	'image_size'              => 'medium_large',
	'cache_enabled'           => true,
	'cache_time'              => HOUR_IN_SECONDS,
	'fallback_enabled'        => true,
	'fallback_orderby'        => 'date',
	'fallback_order'          => 'DESC',
	'fallback_days'           => 365,
	'layout'                  => 'grid',
	'section_id'              => 'rx-related-posts',
	'container_class'         => 'rx-related-posts',
	'card_class'              => 'rx-related-card',
	'more_text'               => __( 'Read More', 'rx-theme' ),
	'no_posts_text'           => '',
);

$rx_related_args = apply_filters( 'rx_related_posts_args', $rx_related_defaults, $current_post_id );

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

$posts_per_page = isset( $rx_related_args['posts_per_page'] ) ? absint( $rx_related_args['posts_per_page'] ) : 6;

if ( $posts_per_page < 1 ) {
	return;
}

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

if ( ! function_exists( 'rx_related_posts_get_reading_time' ) ) {
	/**
	 * Get estimated reading time.
	 *
	 * @param int $post_id Post ID.
	 * @return string
	 */
	function rx_related_posts_get_reading_time( $post_id ) {
		$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(
			_n( '%s min read', '%s min read', $minutes, 'rx-theme' ),
			number_format_i18n( $minutes )
		);
	}
}

if ( ! function_exists( 'rx_related_posts_get_excerpt' ) ) {
	/**
	 * Get clean custom excerpt.
	 *
	 * @param int $post_id Post ID.
	 * @param int $length  Word length.
	 * @return string
	 */
	function rx_related_posts_get_excerpt( $post_id, $length = 18 ) {
		$manual_excerpt = get_the_excerpt( $post_id );

		if ( ! empty( $manual_excerpt ) ) {
			$text = $manual_excerpt;
		} else {
			$text = get_post_field( 'post_content', $post_id );
		}

		$text = wp_strip_all_tags( strip_shortcodes( $text ) );
		$text = wp_trim_words( $text, absint( $length ), '...' );

		return $text;
	}
}

if ( ! function_exists( 'rx_related_posts_get_views' ) ) {
	/**
	 * Get post views if your theme/plugin stores views.
	 *
	 * Supported meta examples:
	 * - post_views_count
	 * - views
	 * - rx_post_views
	 *
	 * @param int $post_id Post ID.
	 * @return int
	 */
	function rx_related_posts_get_views( $post_id ) {
		$possible_keys = apply_filters(
			'rx_related_posts_view_meta_keys',
			array(
				'post_views_count',
				'views',
				'rx_post_views',
			)
		);

		foreach ( $possible_keys as $key ) {
			$value = get_post_meta( $post_id, $key, true );

			if ( '' !== $value && is_numeric( $value ) ) {
				return absint( $value );
			}
		}

		return 0;
	}
}

if ( ! function_exists( 'rx_related_posts_get_primary_category' ) ) {
	/**
	 * Get primary/first category.
	 *
	 * @param int $post_id Post ID.
	 * @return WP_Term|null
	 */
	function rx_related_posts_get_primary_category( $post_id ) {
		$categories = get_the_category( $post_id );

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

		return $categories[0];
	}
}

if ( ! function_exists( 'rx_related_posts_get_tax_query' ) ) {
	/**
	 * Build related posts tax query.
	 *
	 * @param int   $post_id Post ID.
	 * @param array $args    Settings.
	 * @return array
	 */
	function rx_related_posts_get_tax_query( $post_id, $args ) {
		$tax_query = array(
			'relation' => 'OR',
		);

		if ( ! empty( $args['match_categories'] ) ) {
			$category_ids = wp_get_post_terms(
				$post_id,
				'category',
				array(
					'fields' => 'ids',
				)
			);

			if ( ! is_wp_error( $category_ids ) && ! empty( $category_ids ) ) {
				$tax_query[] = array(
					'taxonomy'         => 'category',
					'field'            => 'term_id',
					'terms'            => array_map( 'absint', $category_ids ),
					'include_children' => true,
					'operator'         => 'IN',
				);
			}
		}

		if ( ! empty( $args['match_tags'] ) ) {
			$tag_ids = wp_get_post_terms(
				$post_id,
				'post_tag',
				array(
					'fields' => 'ids',
				)
			);

			if ( ! is_wp_error( $tag_ids ) && ! empty( $tag_ids ) ) {
				$tax_query[] = array(
					'taxonomy' => 'post_tag',
					'field'    => 'term_id',
					'terms'    => array_map( 'absint', $tag_ids ),
					'operator' => 'IN',
				);
			}
		}

		if ( ! empty( $args['match_custom_taxonomies'] ) ) {
			$custom_taxonomies = ! empty( $args['custom_taxonomies'] ) && is_array( $args['custom_taxonomies'] )
				? $args['custom_taxonomies']
				: get_object_taxonomies( get_post_type( $post_id ), 'names' );

			$excluded_taxonomies = array(
				'category',
				'post_tag',
				'post_format',
				'nav_menu',
				'link_category',
			);

			foreach ( $custom_taxonomies as $taxonomy ) {
				if ( in_array( $taxonomy, $excluded_taxonomies, true ) ) {
					continue;
				}

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

				$term_ids = wp_get_post_terms(
					$post_id,
					$taxonomy,
					array(
						'fields' => 'ids',
					)
				);

				if ( ! is_wp_error( $term_ids ) && ! empty( $term_ids ) ) {
					$tax_query[] = array(
						'taxonomy' => sanitize_key( $taxonomy ),
						'field'    => 'term_id',
						'terms'    => array_map( 'absint', $term_ids ),
						'operator' => 'IN',
					);
				}
			}
		}

		if ( count( $tax_query ) <= 1 ) {
			return array();
		}

		return $tax_query;
	}
}

/**
 * ------------------------------------------------------------
 * 3. Cache Key
 * ------------------------------------------------------------
 */

$rx_cache_key = 'rx_related_posts_' . md5(
	wp_json_encode(
		array(
			'post_id'        => $current_post_id,
			'posts_per_page' => $posts_per_page,
			'post_type'      => get_post_type( $current_post_id ),
			'args'           => $rx_related_args,
		)
	)
);

$rx_related_post_ids = false;

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

/**
 * ------------------------------------------------------------
 * 4. Main Related Query
 * ------------------------------------------------------------
 */

if ( false === $rx_related_post_ids ) {
	$post__not_in = array();

	if ( ! empty( $rx_related_args['exclude_current_post'] ) ) {
		$post__not_in[] = $current_post_id;
	}

	$tax_query = rx_related_posts_get_tax_query( $current_post_id, $rx_related_args );

	$query_args = array(
		'post_type'              => ! empty( $rx_related_args['post_types'] ) ? $rx_related_args['post_types'] : array( 'post' ),
		'post_status'            => 'publish',
		'posts_per_page'         => $posts_per_page,
		'post__not_in'           => array_map( 'absint', $post__not_in ),
		'ignore_sticky_posts'    => ! empty( $rx_related_args['exclude_sticky_posts'] ),
		'orderby'                => sanitize_key( $rx_related_args['orderby'] ),
		'order'                  => 'ASC' === strtoupper( $rx_related_args['order'] ) ? 'ASC' : 'DESC',
		'no_found_rows'          => true,
		'update_post_meta_cache' => true,
		'update_post_term_cache' => true,
	);

	if ( ! empty( $tax_query ) ) {
		$query_args['tax_query'] = $tax_query; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
	}

	$query_args = apply_filters( 'rx_related_posts_query_args', $query_args, $current_post_id, $rx_related_args );

	$rx_related_query = new WP_Query( $query_args );

	$rx_related_post_ids = wp_list_pluck( $rx_related_query->posts, 'ID' );

	wp_reset_postdata();

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

	if (
		! empty( $rx_related_args['fallback_enabled'] )
		&& count( $rx_related_post_ids ) < $posts_per_page
	) {
		$needed_posts = $posts_per_page - count( $rx_related_post_ids );

		$fallback_exclude = array_merge(
			array( $current_post_id ),
			array_map( 'absint', $rx_related_post_ids )
		);

		$fallback_date_query = array();

		if ( ! empty( $rx_related_args['fallback_days'] ) ) {
			$fallback_date_query[] = array(
				'after'     => absint( $rx_related_args['fallback_days'] ) . ' days ago',
				'inclusive' => true,
			);
		}

		$fallback_args = array(
			'post_type'              => ! empty( $rx_related_args['post_types'] ) ? $rx_related_args['post_types'] : array( 'post' ),
			'post_status'            => 'publish',
			'posts_per_page'         => $needed_posts,
			'post__not_in'           => array_map( 'absint', $fallback_exclude ),
			'ignore_sticky_posts'    => true,
			'orderby'                => sanitize_key( $rx_related_args['fallback_orderby'] ),
			'order'                  => 'ASC' === strtoupper( $rx_related_args['fallback_order'] ) ? 'ASC' : 'DESC',
			'no_found_rows'          => true,
			'update_post_meta_cache' => true,
			'update_post_term_cache' => true,
		);

		if ( ! empty( $fallback_date_query ) ) {
			$fallback_args['date_query'] = $fallback_date_query;
		}

		$fallback_args = apply_filters( 'rx_related_posts_fallback_query_args', $fallback_args, $current_post_id, $rx_related_args );

		$fallback_query = new WP_Query( $fallback_args );

		$fallback_ids = wp_list_pluck( $fallback_query->posts, 'ID' );

		wp_reset_postdata();

		if ( ! empty( $fallback_ids ) ) {
			$rx_related_post_ids = array_merge( $rx_related_post_ids, $fallback_ids );
			$rx_related_post_ids = array_slice( array_unique( array_map( 'absint', $rx_related_post_ids ) ), 0, $posts_per_page );
		}
	}

	if ( ! empty( $rx_related_args['cache_enabled'] ) ) {
		set_transient(
			$rx_cache_key,
			$rx_related_post_ids,
			! empty( $rx_related_args['cache_time'] ) ? absint( $rx_related_args['cache_time'] ) : HOUR_IN_SECONDS
		);
	}
}

/**
 * ------------------------------------------------------------
 * 6. Stop If Empty
 * ------------------------------------------------------------
 */

if ( empty( $rx_related_post_ids ) ) {
	if ( ! empty( $rx_related_args['no_posts_text'] ) ) {
		echo '<p class="rx-related-posts-empty">' . esc_html( $rx_related_args['no_posts_text'] ) . '</p>';
	}

	return;
}

/**
 * ------------------------------------------------------------
 * 7. Final Display Query
 * ------------------------------------------------------------
 */

$display_query_args = array(
	'post_type'              => ! empty( $rx_related_args['post_types'] ) ? $rx_related_args['post_types'] : array( 'post' ),
	'post_status'            => 'publish',
	'post__in'               => array_map( 'absint', $rx_related_post_ids ),
	'orderby'                => 'post__in',
	'posts_per_page'         => count( $rx_related_post_ids ),
	'no_found_rows'          => true,
	'update_post_meta_cache' => true,
	'update_post_term_cache' => true,
);

$display_query_args = apply_filters( 'rx_related_posts_display_query_args', $display_query_args, $current_post_id, $rx_related_args );

$rx_display_query = new WP_Query( $display_query_args );

if ( ! $rx_display_query->have_posts() ) {
	wp_reset_postdata();
	return;
}

/**
 * ------------------------------------------------------------
 * 8. Classes
 * ------------------------------------------------------------
 */

$section_id      = ! empty( $rx_related_args['section_id'] ) ? sanitize_html_class( $rx_related_args['section_id'] ) : 'rx-related-posts';
$container_class = ! empty( $rx_related_args['container_class'] ) ? sanitize_html_class( $rx_related_args['container_class'] ) : 'rx-related-posts';
$layout          = ! empty( $rx_related_args['layout'] ) ? sanitize_html_class( $rx_related_args['layout'] ) : 'grid';
$columns         = ! empty( $rx_related_args['columns'] ) ? absint( $rx_related_args['columns'] ) : 3;

$wrapper_classes = array(
	$container_class,
	$container_class . '--layout-' . $layout,
	$container_class . '--columns-' . $columns,
);

$wrapper_classes = apply_filters( 'rx_related_posts_wrapper_classes', $wrapper_classes, $current_post_id, $rx_related_args );

?>

<section
	id="<?php echo esc_attr( $section_id ); ?>"
	class="<?php echo esc_attr( implode( ' ', array_map( 'sanitize_html_class', $wrapper_classes ) ) ); ?>"
	aria-labelledby="<?php echo esc_attr( $section_id . '-title' ); ?>"
>
	<header class="rx-related-posts__header">
		<?php if ( ! empty( $rx_related_args['title'] ) ) : ?>
			<h2 id="<?php echo esc_attr( $section_id . '-title' ); ?>" class="rx-related-posts__title">
				<?php echo esc_html( $rx_related_args['title'] ); ?>
			</h2>
		<?php endif; ?>

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

	<div class="rx-related-posts__grid">
		<?php
		while ( $rx_display_query->have_posts() ) :
			$rx_display_query->the_post();

			$related_id       = get_the_ID();
			$related_title    = get_the_title();
			$related_link     = get_permalink();
			$related_category = rx_related_posts_get_primary_category( $related_id );
			$reading_time     = rx_related_posts_get_reading_time( $related_id );
			$views            = rx_related_posts_get_views( $related_id );
			$card_class       = ! empty( $rx_related_args['card_class'] ) ? sanitize_html_class( $rx_related_args['card_class'] ) : 'rx-related-card';

			$article_classes = array(
				$card_class,
				$card_class . '--post-' . absint( $related_id ),
				has_post_thumbnail() ? $card_class . '--has-thumbnail' : $card_class . '--no-thumbnail',
			);

			$article_classes = apply_filters( 'rx_related_posts_card_classes', $article_classes, $related_id, $rx_related_args );
			?>

			<article
				id="rx-related-post-<?php echo esc_attr( $related_id ); ?>"
				<?php post_class( array_map( 'sanitize_html_class', $article_classes ), $related_id ); ?>
				itemscope
				itemtype="https://schema.org/BlogPosting"
			>
				<?php if ( ! empty( $rx_related_args['show_thumbnail'] ) ) : ?>
					<div class="rx-related-card__media">
						<a class="rx-related-card__image-link" href="<?php echo esc_url( $related_link ); ?>" aria-label="<?php echo esc_attr( $related_title ); ?>">
							<?php if ( has_post_thumbnail( $related_id ) ) : ?>
								<?php
								echo get_the_post_thumbnail(
									$related_id,
									! empty( $rx_related_args['image_size'] ) ? esc_attr( $rx_related_args['image_size'] ) : 'medium_large',
									array(
										'class'    => 'rx-related-card__image',
										'loading'  => 'lazy',
										'decoding' => 'async',
										'alt'      => the_title_attribute(
											array(
												'echo' => false,
												'post' => $related_id,
											)
										),
										'itemprop' => 'image',
									)
								);
								?>
							<?php else : ?>
								<div class="rx-related-card__placeholder" aria-hidden="true">
									<span class="rx-related-card__placeholder-icon">📄</span>
								</div>
							<?php endif; ?>
						</a>

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

				<div class="rx-related-card__body">
					<?php if ( ! empty( $rx_related_args['show_post_format'] ) && get_post_format( $related_id ) ) : ?>
						<span class="rx-related-card__format">
							<?php echo esc_html( get_post_format_string( get_post_format( $related_id ) ) ); ?>
						</span>
					<?php endif; ?>

					<h3 class="rx-related-card__title" itemprop="headline">
						<a href="<?php echo esc_url( $related_link ); ?>" itemprop="url">
							<?php echo esc_html( $related_title ); ?>
						</a>
					</h3>

					<div class="rx-related-card__meta">
						<?php if ( ! empty( $rx_related_args['show_date'] ) ) : ?>
							<span class="rx-related-card__date">
								<time datetime="<?php echo esc_attr( get_the_date( DATE_W3C, $related_id ) ); ?>" itemprop="datePublished">
									<?php echo esc_html( get_the_date( '', $related_id ) ); ?>
								</time>
							</span>
						<?php endif; ?>

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

						<?php if ( ! empty( $rx_related_args['show_reading_time'] ) ) : ?>
							<span class="rx-related-card__reading-time">
								<?php echo esc_html( $reading_time ); ?>
							</span>
						<?php endif; ?>

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

						<?php if ( ! empty( $rx_related_args['show_views'] ) && $views > 0 ) : ?>
							<span class="rx-related-card__views">
								<?php
								printf(
									esc_html(
										_n(
											'%s view',
											'%s views',
											$views,
											'rx-theme'
										)
									),
									esc_html( number_format_i18n( $views ) )
								);
								?>
							</span>
						<?php endif; ?>
					</div>

					<?php if ( ! empty( $rx_related_args['show_excerpt'] ) ) : ?>
						<p class="rx-related-card__excerpt" itemprop="description">
							<?php
							echo esc_html(
								rx_related_posts_get_excerpt(
									$related_id,
									! empty( $rx_related_args['excerpt_length'] ) ? absint( $rx_related_args['excerpt_length'] ) : 18
								)
							);
							?>
						</p>
					<?php endif; ?>

					<a class="rx-related-card__read-more" href="<?php echo esc_url( $related_link ); ?>">
						<?php echo esc_html( $rx_related_args['more_text'] ); ?>
						<span class="screen-reader-text">
							<?php echo esc_html( $related_title ); ?>
						</span>
					</a>
				</div>
			</article>

		<?php endwhile; ?>
	</div>
</section>

<?php
wp_reset_postdata();

Add this CSS to your theme style.css or assets/css/related-posts.css:

.rx-related-posts {
	margin-top: 48px;
	margin-bottom: 48px;
}

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

.rx-related-posts__title {
	margin: 0 0 8px;
	font-size: clamp(24px, 3vw, 36px);
	line-height: 1.2;
}

.rx-related-posts__subtitle {
	margin: 0;
	color: #666;
	font-size: 16px;
}

.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: repeat(1, minmax(0, 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-card {
	position: relative;
	overflow: hidden;
	border: 1px solid rgba(0, 0, 0, 0.08);
	border-radius: 18px;
	background: #fff;
	box-shadow: 0 10px 30px rgba(0, 0, 0, 0.06);
	transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.rx-related-card:hover {
	transform: translateY(-4px);
	box-shadow: 0 18px 45px rgba(0, 0, 0, 0.1);
}

.rx-related-card__media {
	position: relative;
	overflow: hidden;
	aspect-ratio: 16 / 10;
	background: #f3f4f6;
}

.rx-related-card__image-link {
	display: block;
	width: 100%;
	height: 100%;
}

.rx-related-card__image {
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform 0.35s ease;
}

.rx-related-card:hover .rx-related-card__image {
	transform: scale(1.04);
}

.rx-related-card__placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 100%;
	font-size: 40px;
	color: #777;
}

.rx-related-card__category {
	position: absolute;
	left: 14px;
	top: 14px;
	z-index: 2;
	display: inline-flex;
	align-items: center;
	border-radius: 999px;
	padding: 6px 12px;
	background: rgba(0, 0, 0, 0.72);
	color: #fff;
	font-size: 12px;
	font-weight: 700;
	text-decoration: none;
	line-height: 1;
}

.rx-related-card__category:hover {
	background: rgba(0, 0, 0, 0.88);
	color: #fff;
}

.rx-related-card__body {
	padding: 20px;
}

.rx-related-card__format {
	display: inline-block;
	margin-bottom: 8px;
	color: #555;
	font-size: 12px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.rx-related-card__title {
	margin: 0 0 12px;
	font-size: 20px;
	line-height: 1.35;
}

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

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

.rx-related-card__meta {
	display: flex;
	flex-wrap: wrap;
	gap: 8px 12px;
	margin-bottom: 12px;
	color: #666;
	font-size: 13px;
	line-height: 1.4;
}

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

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

.rx-related-card__excerpt {
	margin: 0 0 16px;
	color: #555;
	font-size: 15px;
	line-height: 1.65;
}

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

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

.rx-related-posts-empty {
	margin: 32px 0;
	text-align: center;
	color: #777;
}

@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__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;
	}

	.rx-related-card__body {
		padding: 16px;
	}
}

Optional: add this to functions.php to clear related post cache when posts are updated:

/**
 * Clear RX related post transients when posts are saved.
 */
function rx_clear_related_posts_cache_on_save() {
	global $wpdb;

	if ( ! $wpdb instanceof wpdb ) {
		return;
	}

	$wpdb->query(
		$wpdb->prepare(
			"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
			'_transient_rx_related_posts_%',
			'_transient_timeout_rx_related_posts_%'
		)
	);
}
add_action( 'save_post', 'rx_clear_related_posts_cache_on_save' );
add_action( 'deleted_post', 'rx_clear_related_posts_cache_on_save' );
add_action( 'edit_term', 'rx_clear_related_posts_cache_on_save' );
add_action( 'delete_term', 'rx_clear_related_posts_cache_on_save' );

You can customize it from functions.php like this:

function rx_custom_related_posts_settings( $args, $post_id ) {
	$args['title']             = 'More Helpful Articles';
	$args['subtitle']          = 'Selected from similar topics';
	$args['posts_per_page']    = 8;
	$args['columns']           = 4;
	$args['excerpt_length']    = 22;
	$args['show_views']        = true;
	$args['show_comments']     = true;
	$args['show_author']       = true;
	$args['fallback_orderby']  = 'rand';

	return $args;
}
add_filter( 'rx_related_posts_args', 'rx_custom_related_posts_settings', 10, 2 );

My friend, this is a strong production-style related posts file for your RX theme. It is not just simple related posts; it is expandable like a mini related-posts system inside your theme.

Leave a Reply

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