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.