content-search.php

I’ll build this as a full advanced content-search.php template part for your RX theme, including search-result cards, thumbnails, schema-friendly markup, post meta, excerpts, categories/tags, reading time, highlighted search terms, accessibility, and safe WordPress escaping.

Below is an advanced inc/template-parts/content-search.php file for your RX Theme.

Create or replace this file:

rx-theme/
└── inc/
    └── template-parts/
        └── content-search.php
<?php
/**
 * Template part for displaying search results.
 *
 * File: inc/template-parts/content-search.php
 *
 * @package RX_Theme
 */

defined( 'ABSPATH' ) || exit;

global $post;

/**
 * Current post data.
 */
$rx_post_id        = get_the_ID();
$rx_post_type      = get_post_type( $rx_post_id );
$rx_post_type_obj  = get_post_type_object( $rx_post_type );
$rx_post_type_name = $rx_post_type_obj ? $rx_post_type_obj->labels->singular_name : ucfirst( $rx_post_type );

$rx_permalink      = get_permalink( $rx_post_id );
$rx_title          = get_the_title( $rx_post_id );
$rx_search_query   = get_search_query();
$rx_author_id      = get_post_field( 'post_author', $rx_post_id );
$rx_author_name    = get_the_author_meta( 'display_name', $rx_author_id );
$rx_author_url     = get_author_posts_url( $rx_author_id );
$rx_comment_count  = get_comments_number( $rx_post_id );
$rx_modified_time  = get_the_modified_time( 'c', $rx_post_id );
$rx_published_time = get_the_date( 'c', $rx_post_id );

/**
 * Theme helper fallbacks.
 */
if ( ! function_exists( 'rx_get_reading_time' ) ) {
	/**
	 * Estimate reading time.
	 *
	 * @param int $post_id Post ID.
	 * @return string
	 */
	function rx_get_reading_time( $post_id = 0 ) {
		$post_id = $post_id ? absint( $post_id ) : get_the_ID();

		$content    = get_post_field( 'post_content', $post_id );
		$word_count = str_word_count( wp_strip_all_tags( $content ) );
		$minutes    = max( 1, ceil( $word_count / 200 ) );

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

if ( ! function_exists( 'rx_highlight_search_terms' ) ) {
	/**
	 * Highlight search keywords inside text.
	 *
	 * @param string $text Text.
	 * @param string $query Search query.
	 * @return string
	 */
	function rx_highlight_search_terms( $text, $query = '' ) {
		$text  = wp_kses_post( $text );
		$query = trim( wp_strip_all_tags( $query ) );

		if ( empty( $query ) || empty( $text ) ) {
			return $text;
		}

		$terms = preg_split( '/\s+/', $query );

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

		foreach ( $terms as $term ) {
			$term = trim( $term );

			if ( strlen( $term ) < 2 ) {
				continue;
			}

			$pattern = '/' . preg_quote( $term, '/' ) . '/iu';

			$text = preg_replace(
				$pattern,
				'<mark class="rx-search-highlight">$0</mark>',
				$text
			);
		}

		return $text;
	}
}

if ( ! function_exists( 'rx_get_clean_excerpt' ) ) {
	/**
	 * Get clean excerpt.
	 *
	 * @param int $post_id Post ID.
	 * @param int $length Word length.
	 * @return string
	 */
	function rx_get_clean_excerpt( $post_id = 0, $length = 32 ) {
		$post_id = $post_id ? absint( $post_id ) : get_the_ID();

		if ( has_excerpt( $post_id ) ) {
			$excerpt = get_the_excerpt( $post_id );
		} else {
			$content = get_post_field( 'post_content', $post_id );
			$content = strip_shortcodes( $content );
			$content = wp_strip_all_tags( $content );
			$excerpt = wp_trim_words( $content, absint( $length ), '&hellip;' );
		}

		return $excerpt;
	}
}

/**
 * Thumbnail data.
 */
$rx_has_thumbnail = has_post_thumbnail( $rx_post_id );
$rx_thumbnail_id  = $rx_has_thumbnail ? get_post_thumbnail_id( $rx_post_id ) : 0;
$rx_thumbnail_alt = $rx_thumbnail_id ? get_post_meta( $rx_thumbnail_id, '_wp_attachment_image_alt', true ) : '';

if ( empty( $rx_thumbnail_alt ) ) {
	$rx_thumbnail_alt = $rx_title;
}

/**
 * Taxonomy data.
 */
$rx_categories = get_the_category( $rx_post_id );
$rx_tags       = get_the_tags( $rx_post_id );

/**
 * Excerpt.
 */
$rx_excerpt = rx_get_clean_excerpt( $rx_post_id, 36 );

/**
 * Search highlighting.
 */
$rx_display_title   = rx_highlight_search_terms( esc_html( $rx_title ), $rx_search_query );
$rx_display_excerpt = rx_highlight_search_terms( esc_html( $rx_excerpt ), $rx_search_query );

/**
 * CSS classes.
 */
$rx_article_classes = array(
	'rx-search-result',
	'rx-search-card',
	'rx-search-card--' . sanitize_html_class( $rx_post_type ),
	$rx_has_thumbnail ? 'rx-search-card--has-thumbnail' : 'rx-search-card--no-thumbnail',
);

?>

<article id="post-<?php the_ID(); ?>" <?php post_class( $rx_article_classes ); ?> itemscope itemtype="https://schema.org/Article">

	<meta itemprop="mainEntityOfPage" content="<?php echo esc_url( $rx_permalink ); ?>">
	<meta itemprop="datePublished" content="<?php echo esc_attr( $rx_published_time ); ?>">
	<meta itemprop="dateModified" content="<?php echo esc_attr( $rx_modified_time ); ?>">

	<div class="rx-search-card__inner">

		<?php if ( $rx_has_thumbnail ) : ?>
			<figure class="rx-search-card__media">
				<a
					class="rx-search-card__thumbnail-link"
					href="<?php echo esc_url( $rx_permalink ); ?>"
					aria-label="<?php echo esc_attr( sprintf( __( 'Read more about %s', 'rx-theme' ), $rx_title ) ); ?>"
					itemprop="url"
				>
					<?php
					echo wp_get_attachment_image(
						$rx_thumbnail_id,
						'medium_large',
						false,
						array(
							'class'         => 'rx-search-card__thumbnail',
							'alt'           => esc_attr( $rx_thumbnail_alt ),
							'loading'       => 'lazy',
							'decoding'      => 'async',
							'fetchpriority' => 'low',
							'itemprop'      => 'image',
						)
					);
					?>
				</a>
			</figure>
		<?php else : ?>
			<div class="rx-search-card__media rx-search-card__media--placeholder" aria-hidden="true">
				<a class="rx-search-card__placeholder-link" href="<?php echo esc_url( $rx_permalink ); ?>">
					<span class="rx-search-card__placeholder-icon">
						<?php echo esc_html( strtoupper( mb_substr( $rx_post_type_name, 0, 1 ) ) ); ?>
					</span>
				</a>
			</div>
		<?php endif; ?>

		<div class="rx-search-card__content">

			<header class="rx-search-card__header">

				<div class="rx-search-card__top-meta">

					<span class="rx-search-card__post-type">
						<?php echo esc_html( $rx_post_type_name ); ?>
					</span>

					<?php if ( ! empty( $rx_categories ) && 'post' === $rx_post_type ) : ?>
						<span class="rx-search-card__separator" aria-hidden="true">/</span>

						<span class="rx-search-card__category">
							<?php
							$rx_primary_category = $rx_categories[0];

							printf(
								'<a href="%1$s">%2$s</a>',
								esc_url( get_category_link( $rx_primary_category->term_id ) ),
								esc_html( $rx_primary_category->name )
							);
							?>
						</span>
					<?php endif; ?>

				</div>

				<?php the_title( '<h2 class="rx-search-card__title" itemprop="headline"><a href="' . esc_url( $rx_permalink ) . '" rel="bookmark">', '</a></h2>' ); ?>

				<?php if ( $rx_search_query ) : ?>
					<h3 class="screen-reader-text">
						<?php
						printf(
							/* translators: %s: search query */
							esc_html__( 'Search result matched for: %s', 'rx-theme' ),
							esc_html( $rx_search_query )
						);
						?>
					</h3>
				<?php endif; ?>

			</header>

			<div class="rx-search-card__meta" itemprop="author" itemscope itemtype="https://schema.org/Person">

				<span class="rx-search-card__author">
					<?php esc_html_e( 'By', 'rx-theme' ); ?>
					<a href="<?php echo esc_url( $rx_author_url ); ?>" itemprop="url">
						<span itemprop="name"><?php echo esc_html( $rx_author_name ); ?></span>
					</a>
				</span>

				<span class="rx-search-card__meta-separator" aria-hidden="true"></span>

				<time class="rx-search-card__date" datetime="<?php echo esc_attr( $rx_published_time ); ?>">
					<?php echo esc_html( get_the_date() ); ?>
				</time>

				<span class="rx-search-card__meta-separator" aria-hidden="true"></span>

				<span class="rx-search-card__reading-time">
					<?php echo esc_html( rx_get_reading_time( $rx_post_id ) ); ?>
				</span>

				<?php if ( comments_open( $rx_post_id ) || $rx_comment_count ) : ?>
					<span class="rx-search-card__meta-separator" aria-hidden="true"></span>

					<a class="rx-search-card__comments" href="<?php echo esc_url( get_comments_link( $rx_post_id ) ); ?>">
						<?php
						printf(
							/* translators: %s: comment count */
							esc_html( _n( '%s comment', '%s comments', $rx_comment_count, 'rx-theme' ) ),
							esc_html( number_format_i18n( $rx_comment_count ) )
						);
						?>
					</a>
				<?php endif; ?>

			</div>

			<?php if ( ! empty( $rx_excerpt ) ) : ?>
				<div class="rx-search-card__excerpt" itemprop="description">
					<p>
						<?php
						echo wp_kses(
							$rx_display_excerpt,
							array(
								'mark' => array(
									'class' => array(),
								),
							)
						);
						?>
					</p>
				</div>
			<?php endif; ?>

			<?php if ( ! empty( $rx_tags ) && 'post' === $rx_post_type ) : ?>
				<div class="rx-search-card__tags" aria-label="<?php esc_attr_e( 'Post tags', 'rx-theme' ); ?>">
					<?php foreach ( array_slice( $rx_tags, 0, 5 ) as $rx_tag ) : ?>
						<a class="rx-search-card__tag" href="<?php echo esc_url( get_tag_link( $rx_tag->term_id ) ); ?>">
							#<?php echo esc_html( $rx_tag->name ); ?>
						</a>
					<?php endforeach; ?>
				</div>
			<?php endif; ?>

			<footer class="rx-search-card__footer">

				<a
					class="rx-search-card__read-more"
					href="<?php echo esc_url( $rx_permalink ); ?>"
					aria-label="<?php echo esc_attr( sprintf( __( 'Continue reading %s', 'rx-theme' ), $rx_title ) ); ?>"
				>
					<span><?php esc_html_e( 'Read More', 'rx-theme' ); ?></span>
					<span class="rx-search-card__read-more-icon" aria-hidden="true"></span>
				</a>

				<?php if ( current_user_can( 'edit_post', $rx_post_id ) ) : ?>
					<a class="rx-search-card__edit-link" href="<?php echo esc_url( get_edit_post_link( $rx_post_id ) ); ?>">
						<?php esc_html_e( 'Edit', 'rx-theme' ); ?>
					</a>
				<?php endif; ?>

			</footer>

		</div>
	</div>

</article>

Add this CSS to your theme stylesheet:

/* Search Result Card */
.rx-search-result,
.rx-search-card {
	margin-bottom: 28px;
	border: 1px solid rgba(0, 0, 0, 0.08);
	border-radius: 18px;
	background: #fff;
	overflow: hidden;
	transition: box-shadow 0.25s ease, transform 0.25s ease, border-color 0.25s ease;
}

.rx-search-card:hover {
	transform: translateY(-3px);
	box-shadow: 0 12px 32px rgba(0, 0, 0, 0.08);
	border-color: rgba(0, 0, 0, 0.14);
}

.rx-search-card__inner {
	display: grid;
	grid-template-columns: 260px 1fr;
	gap: 0;
}

.rx-search-card__media {
	position: relative;
	margin: 0;
	background: #f5f7fb;
	min-height: 220px;
	overflow: hidden;
}

.rx-search-card__thumbnail-link,
.rx-search-card__placeholder-link {
	display: block;
	width: 100%;
	height: 100%;
}

.rx-search-card__thumbnail {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	transition: transform 0.35s ease;
}

.rx-search-card:hover .rx-search-card__thumbnail {
	transform: scale(1.04);
}

.rx-search-card__media--placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
}

.rx-search-card__placeholder-icon {
	width: 76px;
	height: 76px;
	border-radius: 50%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background: #e9eef8;
	color: #334155;
	font-size: 32px;
	font-weight: 800;
}

.rx-search-card__content {
	padding: 24px;
}

.rx-search-card__top-meta {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-bottom: 8px;
	font-size: 13px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: #64748b;
}

.rx-search-card__post-type {
	color: #2563eb;
}

.rx-search-card__category a {
	color: #64748b;
	text-decoration: none;
}

.rx-search-card__category a:hover {
	color: #2563eb;
}

.rx-search-card__title {
	margin: 0 0 10px;
	font-size: clamp(22px, 3vw, 30px);
	line-height: 1.25;
}

.rx-search-card__title a {
	color: #0f172a;
	text-decoration: none;
}

.rx-search-card__title a:hover {
	color: #2563eb;
}

.rx-search-card__meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px;
	margin-bottom: 14px;
	font-size: 14px;
	color: #64748b;
}

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

.rx-search-card__meta a:hover {
	color: #2563eb;
}

.rx-search-card__excerpt {
	color: #334155;
	font-size: 16px;
	line-height: 1.75;
}

.rx-search-card__excerpt p {
	margin-top: 0;
	margin-bottom: 16px;
}

.rx-search-highlight {
	background: #fff3b0;
	color: inherit;
	padding: 0 2px;
	border-radius: 3px;
}

.rx-search-card__tags {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin-bottom: 18px;
}

.rx-search-card__tag {
	display: inline-flex;
	align-items: center;
	padding: 5px 10px;
	border-radius: 999px;
	background: #f1f5f9;
	color: #475569;
	font-size: 13px;
	text-decoration: none;
}

.rx-search-card__tag:hover {
	background: #dbeafe;
	color: #1d4ed8;
}

.rx-search-card__footer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
}

.rx-search-card__read-more {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	font-weight: 700;
	color: #2563eb;
	text-decoration: none;
}

.rx-search-card__read-more:hover {
	color: #1d4ed8;
}

.rx-search-card__read-more-icon {
	transition: transform 0.25s ease;
}

.rx-search-card__read-more:hover .rx-search-card__read-more-icon {
	transform: translateX(4px);
}

.rx-search-card__edit-link {
	font-size: 13px;
	color: #64748b;
	text-decoration: underline;
}

.screen-reader-text {
	position: absolute !important;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

@media (max-width: 768px) {
	.rx-search-card__inner {
		grid-template-columns: 1fr;
	}

	.rx-search-card__media {
		min-height: 210px;
	}

	.rx-search-card__content {
		padding: 20px;
	}
}

Use this inside your search.php loop:

<?php
if ( have_posts() ) :
	while ( have_posts() ) :
		the_post();

		get_template_part( 'inc/template-parts/content', 'search' );

	endwhile;

	the_posts_pagination(
		array(
			'mid_size'           => 2,
			'prev_text'          => esc_html__( 'Previous', 'rx-theme' ),
			'next_text'          => esc_html__( 'Next', 'rx-theme' ),
			'screen_reader_text' => esc_html__( 'Search results navigation', 'rx-theme' ),
		)
	);

else :
	get_template_part( 'inc/template-parts/content', 'none' );
endif;
?>

Important small correction: in the title section, WordPress the_title() will print the raw title without search highlighting. So, if you want the highlighted title too, replace this part:

<?php the_title( '<h2 class="rx-search-card__title" itemprop="headline"><a href="' . esc_url( $rx_permalink ) . '" rel="bookmark">', '</a></h2>' ); ?>

with this:

<h2 class="rx-search-card__title" itemprop="headline">
	<a href="<?php echo esc_url( $rx_permalink ); ?>" rel="bookmark">
		<?php
		echo wp_kses(
			$rx_display_title,
			array(
				'mark' => array(
					'class' => array(),
				),
			)
		);
		?>
	</a>
</h2>

For best RX Theme structure, keep helper functions like rx_get_reading_time(), rx_highlight_search_terms(), and rx_get_clean_excerpt() later inside:

inc/helpers/template-functions.php

Then load that helper file from functions.php. This keeps content-search.php cleaner and more professional.

Leave a Reply

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