content-search.php

I’ll build this as a strong WordPress theme template part for search results, with safe escaping, schema-friendly markup, thumbnails, meta, excerpt handling, breadcrumbs-ready hooks, post-type badges, reading time fallback, and developer hooks you can extend later.

Yes friend — in rx theme/template-parts/content/content-search.php, this file should display one search result card/item inside search.php. Below is an advanced, safe, SEO-friendly, extendable version.

Create or replace this file:

<?php
/**
 * Template part for displaying search result content.
 *
 * File: template-parts/content/content-search.php
 *
 * @package Rx_Theme
 */

defined( 'ABSPATH' ) || exit;

$post_id        = get_the_ID();
$post_type      = get_post_type( $post_id );
$post_type_obj  = get_post_type_object( $post_type );
$post_type_name = $post_type_obj && ! empty( $post_type_obj->labels->singular_name )
	? $post_type_obj->labels->singular_name
	: ucfirst( $post_type );

$permalink      = get_permalink( $post_id );
$title          = get_the_title( $post_id );
$excerpt        = has_excerpt( $post_id ) ? get_the_excerpt( $post_id ) : wp_trim_words( wp_strip_all_tags( get_the_content( null, false, $post_id ) ), 34, '...' );
$author_id      = get_post_field( 'post_author', $post_id );
$author_name    = get_the_author_meta( 'display_name', $author_id );
$author_url     = get_author_posts_url( $author_id );
$published_time = get_the_date( DATE_W3C, $post_id );
$modified_time  = get_the_modified_date( DATE_W3C, $post_id );
$comments_count = get_comments_number( $post_id );

$has_thumbnail = has_post_thumbnail( $post_id );
$thumbnail_id  = get_post_thumbnail_id( $post_id );

$categories = get_the_category( $post_id );
$tags       = get_the_tags( $post_id );

$search_query = get_search_query();

/**
 * Optional reading time support.
 * If your theme already has rx_reading_time(), it will use that.
 * Otherwise, this template has a small fallback.
 */
if ( ! function_exists( 'rx_content_search_reading_time' ) ) {
	function rx_content_search_reading_time( $post_id = 0 ) {
		$post_id = $post_id ? absint( $post_id ) : get_the_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(
			/* translators: %s: reading time in minutes */
			esc_html__( '%s min read', 'rx-theme' ),
			number_format_i18n( $minutes )
		);
	}
}

$reading_time = function_exists( 'rx_reading_time' )
	? rx_reading_time( $post_id )
	: rx_content_search_reading_time( $post_id );

/**
 * Highlight searched words inside title/excerpt.
 * This is frontend-only highlighting, not changing database content.
 */
if ( ! function_exists( 'rx_content_search_highlight_keywords' ) ) {
	function rx_content_search_highlight_keywords( $text, $query ) {
		$text  = wp_kses_post( $text );
		$query = trim( wp_strip_all_tags( $query ) );

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

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

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

		foreach ( $words as $word ) {
			$word = trim( $word );

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

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

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

		return $text;
	}
}

/**
 * Allow child theme/plugin modification.
 */
$excerpt = apply_filters( 'rx_content_search_excerpt', $excerpt, $post_id );
$title   = apply_filters( 'rx_content_search_title', $title, $post_id );

$highlighted_title   = rx_content_search_highlight_keywords( esc_html( $title ), $search_query );
$highlighted_excerpt = rx_content_search_highlight_keywords( esc_html( $excerpt ), $search_query );

$article_classes = array(
	'rx-search-card',
	'rx-search-card--post-type-' . sanitize_html_class( $post_type ),
	$has_thumbnail ? 'rx-search-card--has-thumbnail' : 'rx-search-card--no-thumbnail',
);

if ( is_sticky( $post_id ) ) {
	$article_classes[] = 'rx-search-card--sticky';
}

?>

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

	<?php
	/**
	 * Hook before search result card.
	 */
	do_action( 'rx_before_content_search_card', $post_id );
	?>

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

		<?php if ( $has_thumbnail ) : ?>
			<figure class="rx-search-card__thumbnail">
				<a
					class="rx-search-card__thumbnail-link"
					href="<?php echo esc_url( $permalink ); ?>"
					aria-label="<?php echo esc_attr( sprintf( __( 'Read: %s', 'rx-theme' ), $title ) ); ?>"
					tabindex="-1"
				>
					<?php
					echo wp_get_attachment_image(
						$thumbnail_id,
						'medium_large',
						false,
						array(
							'class'    => 'rx-search-card__image',
							'loading'  => 'lazy',
							'decoding' => 'async',
							'alt'      => esc_attr( $title ),
							'itemprop' => 'image',
						)
					);
					?>
				</a>
			</figure>
		<?php endif; ?>

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

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

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

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

					<?php if ( is_sticky( $post_id ) ) : ?>
						<span class="rx-search-card__sticky-badge">
							<?php esc_html_e( 'Featured', 'rx-theme' ); ?>
						</span>
					<?php endif; ?>

					<?php if ( ! empty( $categories ) && 'post' === $post_type ) : ?>
						<a
							class="rx-search-card__category"
							href="<?php echo esc_url( get_category_link( $categories[0]->term_id ) ); ?>"
						>
							<?php echo esc_html( $categories[0]->name ); ?>
						</a>
					<?php endif; ?>

				</div>

				<?php if ( $title ) : ?>
					<h2 class="rx-search-card__title" itemprop="headline">
						<a href="<?php echo esc_url( $permalink ); ?>" rel="bookmark" itemprop="url">
							<?php echo wp_kses_post( $highlighted_title ); ?>
						</a>
					</h2>
				<?php else : ?>
					<h2 class="rx-search-card__title" itemprop="headline">
						<a href="<?php echo esc_url( $permalink ); ?>" rel="bookmark" itemprop="url">
							<?php esc_html_e( 'Untitled Search Result', 'rx-theme' ); ?>
						</a>
					</h2>
				<?php endif; ?>

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

					<span class="rx-search-card__date">
						<svg class="rx-search-card__icon" width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
							<path d="M7 2v2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2V2h-2v2H9V2H7zm12 8v10H5V10h14z"></path>
						</svg>

						<time datetime="<?php echo esc_attr( $published_time ); ?>" itemprop="datePublished">
							<?php echo esc_html( get_the_date( '', $post_id ) ); ?>
						</time>
					</span>

					<span class="rx-search-card__updated screen-reader-text">
						<time datetime="<?php echo esc_attr( $modified_time ); ?>" itemprop="dateModified">
							<?php echo esc_html( get_the_modified_date( '', $post_id ) ); ?>
						</time>
					</span>

					<?php if ( $author_name ) : ?>
						<span class="rx-search-card__author" itemprop="author" itemscope itemtype="https://schema.org/Person">
							<svg class="rx-search-card__icon" width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
								<path d="M12 12a5 5 0 1 0-5-5 5 5 0 0 0 5 5zm0 2c-4.33 0-8 2.17-8 5v1h16v-1c0-2.83-3.67-5-8-5z"></path>
							</svg>

							<a href="<?php echo esc_url( $author_url ); ?>" itemprop="url">
								<span itemprop="name"><?php echo esc_html( $author_name ); ?></span>
							</a>
						</span>
					<?php endif; ?>

					<?php if ( $reading_time ) : ?>
						<span class="rx-search-card__reading-time">
							<svg class="rx-search-card__icon" width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
								<path d="M12 2a10 10 0 1 0 10 10A10.01 10.01 0 0 0 12 2zm1 11h5v-2h-4V6h-2v7z"></path>
							</svg>
							<?php echo esc_html( $reading_time ); ?>
						</span>
					<?php endif; ?>

					<?php if ( comments_open( $post_id ) || $comments_count > 0 ) : ?>
						<span class="rx-search-card__comments">
							<svg class="rx-search-card__icon" width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
								<path d="M20 2H4a2 2 0 0 0-2 2v18l4-4h14a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z"></path>
							</svg>

							<a href="<?php echo esc_url( get_comments_link( $post_id ) ); ?>">
								<?php
								printf(
									/* translators: %s: number of comments */
									esc_html( _n( '%s comment', '%s comments', $comments_count, 'rx-theme' ) ),
									esc_html( number_format_i18n( $comments_count ) )
								);
								?>
							</a>
						</span>
					<?php endif; ?>

				</div>

			</header>

			<?php if ( ! empty( $excerpt ) ) : ?>
				<div class="rx-search-card__excerpt" itemprop="description">
					<p><?php echo wp_kses_post( $highlighted_excerpt ); ?></p>
				</div>
			<?php endif; ?>

			<?php if ( ! empty( $tags ) && is_array( $tags ) ) : ?>
				<div class="rx-search-card__tags" aria-label="<?php esc_attr_e( 'Post tags', 'rx-theme' ); ?>">
					<?php
					$tag_limit = 4;
					$tag_count = 0;

					foreach ( $tags as $tag ) :
						if ( $tag_count >= $tag_limit ) {
							break;
						}
						?>
						<a
							class="rx-search-card__tag"
							href="<?php echo esc_url( get_tag_link( $tag->term_id ) ); ?>"
							rel="tag"
						>
							#<?php echo esc_html( $tag->name ); ?>
						</a>
						<?php
						$tag_count++;
					endforeach;
					?>
				</div>
			<?php endif; ?>

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

				<a class="rx-search-card__read-more" href="<?php echo esc_url( $permalink ); ?>">
					<span>
						<?php esc_html_e( 'Read More', 'rx-theme' ); ?>
					</span>

					<svg class="rx-search-card__read-more-icon" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
						<path d="M13.172 12 8.222 7.05l1.414-1.414L16 12l-6.364 6.364-1.414-1.414z"></path>
					</svg>
				</a>

				<?php
				/**
				 * Hook inside search result footer.
				 */
				do_action( 'rx_content_search_footer', $post_id );
				?>

			</footer>

		</div>

	</div>

	<meta itemprop="mainEntityOfPage" content="<?php echo esc_url( $permalink ); ?>">
	<meta itemprop="datePublished" content="<?php echo esc_attr( $published_time ); ?>">
	<meta itemprop="dateModified" content="<?php echo esc_attr( $modified_time ); ?>">

	<?php if ( $author_name ) : ?>
		<meta itemprop="author" content="<?php echo esc_attr( $author_name ); ?>">
	<?php endif; ?>

	<?php
	/**
	 * Hook after search result card.
	 */
	do_action( 'rx_after_content_search_card', $post_id );
	?>

</article>

Add this CSS to your theme style.css or assets/css/search.css:

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

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

.rx-search-card__inner {
	display: grid;
	grid-template-columns: 280px minmax(0, 1fr);
	gap: 0;
}

.rx-search-card--no-thumbnail .rx-search-card__inner {
	display: block;
}

.rx-search-card__thumbnail {
	margin: 0;
	position: relative;
	background: #f4f6f8;
	min-height: 100%;
}

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

.rx-search-card__image {
	width: 100%;
	height: 100%;
	min-height: 230px;
	object-fit: cover;
	display: block;
}

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

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

.rx-search-card__post-type,
.rx-search-card__sticky-badge,
.rx-search-card__category {
	display: inline-flex;
	align-items: center;
	border-radius: 999px;
	padding: 5px 11px;
	font-size: 12px;
	font-weight: 700;
	line-height: 1.2;
	text-decoration: none;
}

.rx-search-card__post-type {
	background: #eef2ff;
	color: #3730a3;
}

.rx-search-card__sticky-badge {
	background: #fff7ed;
	color: #c2410c;
}

.rx-search-card__category {
	background: #ecfdf5;
	color: #047857;
}

.rx-search-card__category:hover {
	text-decoration: underline;
}

.rx-search-card__title {
	margin: 0 0 12px;
	font-size: clamp(22px, 3vw, 32px);
	line-height: 1.22;
	font-weight: 800;
}

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

.rx-search-card__title a:hover {
	color: #2563eb;
	text-decoration: underline;
	text-underline-offset: 4px;
}

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

.rx-search-card__meta {
	display: flex;
	flex-wrap: wrap;
	gap: 10px 18px;
	margin-bottom: 16px;
	color: #6b7280;
	font-size: 14px;
}

.rx-search-card__meta span,
.rx-search-card__meta a {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	color: inherit;
	text-decoration: none;
}

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

.rx-search-card__icon {
	fill: currentColor;
	opacity: 0.75;
}

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

.rx-search-card__excerpt p {
	margin: 0 0 18px;
}

.rx-search-card__tags {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 16px 0 20px;
}

.rx-search-card__tag {
	font-size: 13px;
	text-decoration: none;
	color: #4b5563;
	background: #f3f4f6;
	border-radius: 999px;
	padding: 5px 10px;
}

.rx-search-card__tag:hover {
	background: #e5e7eb;
	color: #111827;
}

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

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

.rx-search-card__read-more:hover {
	text-decoration: underline;
	text-underline-offset: 4px;
}

.rx-search-card__read-more-icon {
	fill: currentColor;
	transition: transform 0.2s ease;
}

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

.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: 860px) {
	.rx-search-card__inner {
		grid-template-columns: 1fr;
	}

	.rx-search-card__image {
		min-height: 220px;
	}

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

@media (max-width: 520px) {
	.rx-search-card {
		border-radius: 14px;
	}

	.rx-search-card__body {
		padding: 18px;
	}

	.rx-search-card__meta {
		gap: 8px 12px;
		font-size: 13px;
	}

	.rx-search-card__footer {
		align-items: flex-start;
		flex-direction: column;
	}
}

Use it inside search.php like this:

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

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

	endwhile;

	the_posts_pagination();

else :

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

endif;
?>

This version includes:

Safe escaping, search keyword highlighting, featured image, post type badge, category badge, sticky badge, title, excerpt, author, date, modified date schema, reading time, comments count, tags, read-more button, lazy image loading, schema-friendly article markup, accessibility support, and WordPress action/filter hooks for future expansion.

Leave a Reply

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