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 ), '…' );
}
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.