WidgetCoreWidgetCore Documentation

Live Search widget

The complete guide to the WidgetCore Live Search widget — General, Layout, Query and Pagination controls, result structure, styles, JavaScript configuration and how to send custom queries.

The Live Search widget sends the visitor's query to WordPress' REST API and renders the results without a page reload. Everything the widget does is built on top of a real search form, so if JavaScript is unavailable the form still submits and the theme's native search results page opens.

ItemValue
Widget namewgcr-search
Panel titleLive Search
CategoryWidgetCore Widgets (wgcr)
Style / scriptwgcr-search
Root class.wgcr-search
REST route/wp-json/wgcr/v1/search

General section

ControlTypeDefaultNotes
Results display modeSelectList modeList mode (dropdown panel) or Page mode (always-visible region)
Placeholder textTextEmptyFilled automatically: “Search in posts…”, or a post-type aware text such as “Search in courses…”
Show thumbnailSwitcherYesFeatured image next to each result
Show excerptSwitcherYes18 words with an ellipsis
“Show all” textText“Show all results”Label of the link to the site search page

Results display mode is the most important choice; it changes the whole experience and the list of available controls. See the display modes page.

Display modes

The live search widget has two display modes, and this single control decides where results are shown, which extra controls are available and how visitors navigate them.

Comparing the two modes

FeatureList mode (modal)Page mode (page)
Where results appearA dropdown panel below the fieldAn always-visible region inside the same widget
Initial panel stateClosed with hiddenOpen (aria-expanded="true" on the field)
Closes on an outside clickYesNo (the panel is part of the page)
Escape behaviourCloses the panel; if already closed, clears the queryClears the search query
ColumnsAlways a single column1 to 6 columns (responsive)
Masonry and equal heightNot availableAvailable
Results container stylingPanel background and radius onlyWidth, minimum/maximum height, spacing, background, alignment
Infinite scrollInside the panel (panel scroll)Relative to the browser window
“Show all” linkShown after the resultsOnly when the query is valid
Good fit forHeader, sidebar, quick searchA dedicated search page, an archive, a course section

List mode

The default mode. The panel opens after the visitor types two or more characters and closes on an outside click, with Escape, or when the widget loses focus.

[ Search field ▾ ]
└──────────────────────────────┐
│  8 results            All text │
│  ┌────┐ Result title           │
│  │ img│ Excerpt…                │
│  └────┘ 2026-09-01             │
│  ┌────┐ Result title           │
└──────────────────────────────┘
FeatureBehaviour
OpeningAfter 2 characters are typed (250 ms debounce)
ClosingOutside click, Escape, or selecting a result
Max height70vh (adjustable in the “Results box” section)
ColumnsAlways 1
MasonryNot available
PaginationNumbers, previous/next, load on click, load on scroll
  1. The visitor clicks the field

    If a valid query is already in the field and results are still loaded, the panel opens.

  2. The visitor types

    After 250 milliseconds the request is sent and the panel opens.

  3. A result is picked with the keyboard or the mouse

    The up and down arrows move between results and Enter opens the selected one.

  4. The panel closes with an outside click or Escape

    A second Escape clears the text in the field.

Page mode

The results region is always visible inside the widget, so it behaves like a real search page: the visitor types a query and results appear below in the same view.

FeatureBehaviour
Results visibilityAlways visible (no outside click to close)
Layout controlsTemplate choice, items per page, columns, masonry, equal height
Columns1 to 6, responsive (defaults 3 / 2 / 1)
MasonryAvailable
Equal heightAvailable
TemplateDefault or a custom Elementor template
PaginationInside the same region, below the results

Columns, masonry and equal height

SettingEffect
ColumnsThe wgcr-search--cols-{n} class on the root; with 3 or more, a CSS grid
MasonryThe wgcr-search--masonry class; items are positioned by their natural height
Equal heightThe wgcr-search--equal class; all cards stretch to the tallest one

Choosing a template in Page mode

When “Template choice” is set to Custom, the widget sends the template id to the REST route and each result is rendered with your Elementor template:

  1. Build the template in the library

    “Templates → Saved Templates”; the template must be published.

  2. Select it in the widget

    “Template choice” → Custom, then pick the template.

  3. Check the output

    The root gets the wgcr-search--tpl class and the template's own CSS loads once.

Details on the Elementor templates page.

Behaviour differences that are easy to miss

Selecting with the mouse

  • List mode: moving the mouse over a result marks it as “selected” so Enter opens it.
  • Both modes with an Elementor template: when results are rendered by a custom template (wgcr-search-item--tpl), hovering no longer marks a result as selected and the selected styling is not applied — the template has its own links and styles, and overlapping them would break clicks. If a templated result has no link of its own, clicking the card opens that result.

Focus after pagination

When you change page with the keyboard, focus returns to the button you pressed (previous, next, or the current page number) after the new results are rendered. Keyboard users therefore never lose their place in the pagination bar.

Infinite scroll

In Page mode a sentinel element (.wgcr-search-sentinel) measures the end of the results with IntersectionObserver against the browser window, so loading more does not require scrolling inside a panel. In List mode the panel's own scroll is what counts.

Accessibility in both modes

FeatureList modePage mode
role="combobox" on the fieldYesYes
aria-expanded and aria-controlsYesYes
role="listbox" on the results panelYesYes
aria-live="polite" for status messagesYesYes
Result navigation with ArrowDown / ArrowUpYesYes
Opening a result with EnterYesYes
Closing with EscapeYes— (there is nothing to close)

Full details on the accessibility page and the keyboard shortcuts page.

Style differences

AreaList modePage mode
Root classwgcr-search--modalwgcr-search--page
Results backgroundThe dropdown panel backgroundThe widget container background
Max height70vhUnlimited (follows the container)
Grid columns11 to 6

Because both modes share the same root class (.wgcr-search), you can write one CSS rule for the shared parts and add mode-specific rules with the modifier classes.

Which mode to choose

Your needMode
A compact search field in the header or sidebarList mode
A search page with a grid and paginationPage mode
Showing results with a custom card designPage mode + custom template
Infinite scroll while browsingPage mode + “Load on scroll”
A search form inside an Elementor page that looks like a panelList mode
ScenarioSettings
Quick search in the headerList mode, 5 results, no thumbnail, pagination “None”
The main search pagePage mode, 12 results, 3 columns, numbered pagination
A course gridPage mode, source course, columns 3/2/1, masonry on, load on scroll
Results with a bespoke designPage mode, custom template, 2 columns, previous/next pagination

Elementor templates

Besides the built-in card, the live search can render each result with an Elementor template you designed yourself. This gives you full control over the result markup while the plugin keeps handling the query, pagination and accessibility.

Template choiceValueMarkup
Default templatedefaultThe plugin's card: thumbnail, title, excerpt, date
CustomcustomYour Elementor template, rendered per result

Preparing the template

  1. Create a template in the library

    “Templates → Saved Templates → Add New”, type “Section”.

  2. Design one result card

    Use Dynamic Tags for the post fields; the template is rendered in the context of each result.

  3. Publish the template

    An unpublished template cannot be selected.

  4. Select it in the widget

    “Template choice” → Custom, then pick the template from the list.

When a template is selected, the request carries the template id:

GET /wp-json/wgcr/v1/search?q=course&limit=12&type=post&template=42

The server renders the template for every matching post:

php
$plugin   = \Elementor\Plugin::instance();
$document = $plugin->documents->get( $template_id );
$html     = $plugin->frontend->get_builder_content_for_display( $template_id );

The response then includes an html field per result alongside id, title, link, thumb, excerpt and date. The client inserts that HTML directly, which is why no escaping happens twice.

Checking for visible content

Before the rendered template is used, the plugin strips tags and checks whether anything visible is left:

php
$visible = trim( wp_strip_all_tags( $html ) );

If the result is empty — because the template has no content, or its conditions do not match — the plugin falls back to the default card. This prevents the results area from turning into a blank block.

How the template CSS is loaded

Elementor prints each template's CSS with enqueue_css. Since search results are injected after the page has loaded, the plugin takes two steps:

  1. Inline critical CSS: post_css for the template is printed inline once per page, so the injected markup is styled immediately.
  2. Register the template style handle: elementor-post-{id}.css is added to the widget's style dependencies so it loads on pages that contain the widget.
SituationResult
First search on the pageThe template CSS is already present (inline)
Subsequent searchesNo extra request
A template with external fonts or iconsThey are loaded by Elementor as usual

Entry animations and handlers

Elementor elements often carry animations or interactions (hover effects, counters, carousels). Because the markup is injected after page load, those handlers are not bound automatically. The plugin therefore runs:

js
if ( window.elementorFrontend ) {
	window.elementorFrontend.elementsHandler.runReadyTrigger( container );
}

on the injected results container, so animations and interactions defined in the template work as if the content had been printed server-side.

Templated results and interaction

FeatureDefault cardCustom template
Root class—wgcr-search--tpl
role="option" on each resultYesYes
Keyboard navigation (ArrowDown/ArrowUp)YesYes
Opening with EnterYesYes (the first link inside the result)
Thumbnail controlAppliesDoes not apply (your template decides)
Excerpt controlAppliesDoes not apply
┌──────────────────────────────────────────┐
│ ┌──────┐  Title (H4 or H3)               │
│ │ thumb│  Excerpt (2 lines, clamp)       │
│ └──────┘  Category • Date                │
└──────────────────────────────────────────┘
ElementSuggested setting
TitleHeading, linked to the post URL, size H4
ThumbnailFeatured image, square, object-fit: cover
ExcerptText, 18 words with an ellipsis
MetaPost info (category, date) in small text
CardBackground, radius and a soft shadow
Height“Equal height” in the widget for a tidy grid

Template troubleshooting

SymptomCauseFix
Results appear as the default cardThe template rendered no visible contentCheck the template content and its display conditions
The template is not in the listIt is not published, or it is the wrong typePublish it as a “Section” template
The template appears unstyledIts CSS did not loadClear caches; check that elementor-post-{id}.css is requested
Animations do not runelementorFrontend is not availableCheck that Elementor's scripts load on the page
Layout breaks in a gridThe template has its own widthRemove fixed widths inside the template and let the widget grid handle sizing

Query and filters

Every search in the plugin ends in one WP_Query. This page shows what goes into that query, what you can control without writing code, and the two filters that let you change the query on the server.

Base query arguments

ArgumentValueNotes
sThe typed queryWordPress' own search matching
post_typeThe sanitised sourceInvalid values become post
post_statuspublishDrafts and private posts never appear
has_passwordfalsePassword-protected posts are excluded
posts_per_pageThe requested limit, clamped to 1–50—
pagedThe requested pageOnly when greater than 0
orderby / orderPer the controlsWith relevance, no orderby is added
ignore_sticky_postsPer the ignore_sticky value—
tax_queryBuilt from terms and terms_opOnly when terms are given
date_queryBuilt from dateOnly when not all

Filters without code

The tax_query structure

With terms_op="include" several terms are combined with an OR relation:

php
'tax_query' => array(
	'relation' => 'OR',
	array(
		'taxonomy' => 'category',
		'field'    => 'term_id',
		'terms'    => array( 12, 18 ),
		'operator' => 'IN',
	),
),

With terms_op="exclude" a single entry excludes the terms:

php
'tax_query' => array(
	array(
		'taxonomy' => 'category',
		'field'    => 'term_id',
		'terms'    => array( 12, 18 ),
		'operator' => 'NOT IN',
	),
),

The terms format

terms="12,18"                    → category:12, category:18
terms="category:12,post_tag:7"   → mixed taxonomies
terms="product_cat:33"           → a WooCommerce category

Each item is split on the colon; when the taxonomy part is missing, category is assumed. Ids are cast to positive integers and anything that is not a number is dropped.

Server-side filters

Two filters let you change the final arguments:

FilterArgumentsWhen it runs
wgcr_search_query_args$args, $query_id, $requestFor every search request
wgcr_search_query_args/{query_id}$args, $requestOnly for a widget or shortcode with that Query ID

The general filter runs first, so the id-specific one always has the final say.

Where to write them

php
/* A small custom plugin, or the theme's functions.php */
add_filter( 'wgcr_search_query_args', 'my_search_args', 10, 3 );

function my_search_args( $args, $query_id, $request ) {
	/* your changes */
	return $args;
}

Practical examples

Search several post types at once

php
add_filter( 'wgcr_search_query_args', function ( $args ) {
	$args['post_type'] = array( 'post', 'page', 'course' );
	return $args;
} );

Restrict one widget by Query ID

php
add_filter( 'wgcr_search_query_args/featured', function ( $args, $request ) {
	$args['meta_query'] = array(
		array( 'key' => 'featured', 'value' => 'yes' ),
	);
	return $args;
}, 10, 2 );

Combine terms with AND instead of OR

php
add_filter( 'wgcr_search_query_args', function ( $args ) {
	if ( ! empty( $args['tax_query'] ) && 'OR' === ( $args['tax_query']['relation'] ?? '' ) ) {
		$args['tax_query']['relation'] = 'AND';
	}
	return $args;
} );

Search post titles only

php
add_filter( 'posts_search', function ( $search, $query ) {
	if ( ! empty( $query->get( 'wgcr_title_only' ) ) ) {
		global $wpdb;
		$term = '%' . $wpdb->esc_like( $query->get( 's' ) ) . '%';
		$search = " AND {$wpdb->posts}.post_title LIKE '{$term}' ";
	}
	return $search;
}, 10, 2 );

add_filter( 'wgcr_search_query_args', function ( $args, $query_id ) {
	if ( 'titles' === $query_id ) {
		$args['wgcr_title_only'] = 1;
	}
	return $args;
}, 10, 2 );

Exclude a category site-wide

php
add_filter( 'wgcr_search_query_args', function ( $args ) {
	$args['tax_query'][] = array(
		'taxonomy' => 'category',
		'field'    => 'term_id',
		'terms'    => array( 99 ),
		'operator' => 'NOT IN',
	);
	return $args;
} );

Performance notes

SituationRecommendation
A large site with many postsKeep limit at 12 or less and avoid orderby=rand
A meta_query on an unindexed meta keyAdd a database index for that key
A heavy tax_query with many termsNarrow the term list; NOT IN on a large set is slow
Repeated popular queriesCache the route response for anonymous visitors
Random ordering (rand)Costs a full sort on every request; use it sparingly

Pagination

Pagination only matters when the number of matches is larger than “Items per page”. The widget offers four types, and each one uses a different mechanism: replacing the current results or appending new ones.

TypeControl valueBehaviourResults
NonenoneOnly the first page of results—
NumbersnumbersA page bar with previous/next and “Page X of Y”Replaced
Previous / nextprev_nextTwo buttonsReplaced
Load on clickload_on_clickA “Load more” buttonAppended
Load on scrollload_on_scrollAutomatic near the end of the listAppended

The four pagination types

Numbers is the most complete type: a bar with page buttons, previous and next links and an accessible “Page X of Y” text. Because it needs the total number of results, the request carries page and the server returns the X-WP-Total and X-WP-TotalPages headers.

Previous and next uses the same mechanism but only renders two buttons, which suits narrow layouts.

Load on click appends the next page to the existing list with a button click. The visitor keeps their scroll position and can go back to earlier results without a request.

Load on scroll replaces the button with an IntersectionObserver: when the sentinel element comes within about 300 pixels of the bottom of the viewport, the next page loads automatically.

The page bar markup

html
<nav class="wgcr-search-pager" aria-label="Pagination">
	<button type="button" class="wgcr-search-pg wgcr-search-pg--prev">Previous</button>
	<button type="button" class="wgcr-search-pg is-active" aria-current="page">1</button>
	<button type="button" class="wgcr-search-pg">2</button>
	<button type="button" class="wgcr-search-pg">3</button>
	<button type="button" class="wgcr-search-pg wgcr-search-pg--next">Next</button>
	<span class="wgcr-search-pageof" aria-live="polite">Page 1 of 3</span>
</nav>
ClassRole
.wgcr-search-pagerThe nav wrapper with an accessible label
.wgcr-search-pgEach page button
.is-activeThe current page, with aria-current="page"
.wgcr-search-pg--prev / --nextPrevious and next buttons
.wgcr-search-pageofThe “Page X of Y” text inside a polite live region
ControlIdWhereValues
Pagination typepagination_type“Pagination” sectionNone / Numbers / Previous and next / Load on click / Load on scroll
Pagination alignmentpagination_align“Pagination” sectionRight / Centre / Left / Justify, responsive; shown when pagination is not “None”
Spacerpagination_spacer“Pagination” sectionWith / Without; adds the wgcr-search--spacer class
Items per pageposts_per_page“Layout” section (Page mode)1 to 50, default 12
Button textbutton_text“Pagination” sectionDefault “View more posts”; load on click only
Button iconbutton_icon“Pagination” sectionShow / Hide; load on click only
Button IDbutton_id“Pagination” sectionPrinted as the button's id; leave empty to get a generated one
Message alignmentno_posts_align“Pagination” sectionRight / Centre / Left / Justify; load on click and load on scroll
Custom messagecustom_message“Pagination” sectionOff by default; load on click and load on scroll
Custom message textcustom_message_text“Pagination” sectionReplaces “No more posts.” when the switcher is on

The “Spacer” control only adds vertical space before the bar; it does not change behaviour.

How the page count is computed

js
const totalPages = Number( response.headers.get( 'X-WP-TotalPages' ) ) || 1;
const current    = Number( params.get( 'page' ) ) || 1;

The server sends these headers only when page is greater than 0. That means a plain request without page returns no pagination headers, which is why the widget always sends page when a pagination type is active.

Infinite scroll

With load_on_scroll the widget builds a sentinel element at the end of the list:

js
const io = new IntersectionObserver( ( entries ) => {
	if ( entries[ 0 ].isIntersecting && ! loading && hasMore ) {
		loadPage( current + 1, true );
	}
}, { rootMargin: '300px' } );
SafeguardWhy
loading flagPrevents two requests at once
hasMore flagStops requests after the last page
rootMargin: 300pxLoads slightly before the visitor reaches the end
Disconnect on resetThe observer is removed when the query changes

Keyboard behaviour

KeyResult
TabMoves through the page buttons in order
Enter or SpaceActivates the focused page button
ArrowDown / ArrowUpNavigate the results list (when focus is on the field or inside the results)

After a page change, focus returns to the button the visitor pressed — not to the top of the page — so screen-reader users and keyboard users keep their place in the interface.

Performance notes

  • Items per page: the default 12 is a good balance. Above 24 results the payload grows noticeably, especially with thumbnails.
  • Thumbnails: every result with an image adds a request (unless cached). Turning “Show thumbnail” off makes long lists much lighter.
  • Templates: a custom Elementor template renders each result on the server, so more items per page means more work per request. With a heavy template keep “Items per page” at 6 to 9.
  • Debounce: queries are debounced at 250 ms and every in-flight request is aborted with AbortController, so fast typing does not produce a burst of requests.

Choosing a type

Your caseBest type
A search page with many resultsNumbers
A narrow header panelPrevious and next, or Load on click
Browsing a long list without interactionLoad on scroll
A small fixed number of resultsNone

Styling

In Page mode, extra sections control the container and the cards (background, spacing, border radius, shadow and typography).

The search widget is styled in two layers: Elementor style controls (which print inline CSS on the widget) and CSS classes you can target from your theme. This page covers both, plus ready-made snippets.

CSS variables

The plugin defines component-scoped CSS custom properties on each .wgcr-search root. They are not site-wide theme variables; they provide defaults for the widget’s accent colours, borders, radius and responsive column count. Override them on the root of one widget to change only that instance.

VariableDefaultPurpose
--wgcr-blue#2E86DEMain accent: focus border, active icon and spinner
--wgcr-navy#1B5EA8Darker shades of the main accent
--wgcr-ink#111827Heading text colour
--wgcr-slate#64748BSecondary text: excerpt, date and icon
--wgcr-line#E2E8F0Border colour
--wgcr-radius14pxField, panel and card radius
--wgcr-search-cols1Column count in Page mode; set by the --cols-1 … --cols-6 root classes

For example, change the palette for one widget:

css
.wgcr-search {
	--wgcr-blue: #4f46e5;
	--wgcr-navy: #3730a3;
	--wgcr-ink: #0f172a;
	--wgcr-slate: #64748b;
	--wgcr-line: #e2e8f0;
	--wgcr-radius: 10px;
}

The classes below are the stable hooks:

ClassApplied toMeaning
.wgcr-searchRootEvery search instance
.wgcr-search--modalRootList mode
.wgcr-search--pageRootPage display mode
.wgcr-search--cols-1 … --cols-6RootColumn count
.wgcr-search--masonryRootMasonry layout
.wgcr-search--equalRootEqual-height cards
.wgcr-search--tplRootA custom Elementor template is in use
.wgcr-search--spacerRootExtra space before pagination
.wgcr-search-fieldThe inputThe search field
.wgcr-search-panelThe panelThe results container
.wgcr-search-listThe listThe results grid or list
.wgcr-search-itemEach resultOne result card
.wgcr-search-linkEach resultThe result link
.wgcr-search-thumbEach resultThe thumbnail
.wgcr-search-titleEach resultThe title
.wgcr-search-excerptEach resultThe excerpt
.wgcr-search-dateEach resultThe date
.wgcr-search-msgThe panel“No results” and error messages
.wgcr-search-pagerThe panelThe pagination bar
.wgcr-search-statusThe panelThe aria-live status text

Elementor style controls

Search field

ControlDefaultEffect
Backgroundrgba(255,255,255,0.85)Field background
Background (focus)—When the field has focus
Text colour—The typed query
Border colour—Field border
Border colour (focus)—Border on focus
Placeholder colour#94a3b8Placeholder text
Font size—Field text size
Border radius12pxField corner radius

Results box

ControlDefaultEffect
Background#ffffffPanel background
Border colour—Panel border
Border radius14pxPanel corner radius
Max height70vhPanel height cap in List mode
Message text colour#94a3b8“No results” and error messages

Items

ControlDefaultEffect
Padding12pxSpace inside each result
Text colour#0f172aResult title
Background (hover)#f1f5f9The focused or hovered result
Separator colour#f1f5f9Line between results
Thumbnail width72pxThumbnail size
Thumbnail border radius10pxThumbnail corners
Excerpt text colour#64748bExcerpt text
Excerpt font size—Excerpt size

Columns, masonry and equal height

These three settings only appear in Page mode and map directly to root classes:

SettingClassLayout
Columns 3wgcr-search--cols-3grid-template-columns: repeat(3, minmax(0,1fr))
Columns 2wgcr-search--cols-2grid-template-columns: repeat(2, minmax(0,1fr))
Columns 1wgcr-search--cols-1A single column list
Masonrywgcr-search--masonryItems keep their natural height
Equal heightwgcr-search--equalAll cards stretch to the tallest

On screens narrower than 782px the grid drops to a single column automatically, and below 480px the thumbnail shrinks so results stay readable on small phones.

Ready-to-use snippets

A card with a soft shadow and a coloured edge

css
.wgcr-search-item {
	border-radius: 12px;
	box-shadow: 0 2px 12px rgb(15 23 42 / 8%);
	border-inline-start: 3px solid #2563eb;
	transition: transform .18s ease, box-shadow .18s ease;
}
.wgcr-search-item:hover {
	transform: translateY(-2px);
	box-shadow: 0 8px 24px rgb(15 23 42 / 12%);
}

A dark panel

css
.wgcr-search-panel {
	background: #0f172a;
	border-color: #1e293b;
	color: #e2e8f0;
}
.wgcr-search-title { color: #f8fafc; }
.wgcr-search-excerpt { color: #94a3b8; }
.wgcr-search-item:hover { background: #1e293b; }
.wgcr-search-msg { color: #64748b; }

A panel as wide as the field in List mode

css
.wgcr-search--modal .wgcr-search-panel {
	position: absolute;
	inset-inline: 0;
	width: 100%;
	max-width: none;
}

Removing the thumbnail for a compact layout

css
.wgcr-search-thumb { display: none; }
.wgcr-search-item { padding: 8px 12px; }
.wgcr-search-link { gap: 0; }

Important notes

  • Do not edit search.css directly: the file is replaced on every update. Put your CSS in the theme's stylesheet or in Elementor's “Custom CSS”.
  • Respect prefers-reduced-motion: if you add transitions, disable them for visitors who asked for reduced motion:
css
@media (prefers-reduced-motion: reduce) {
	.wgcr-search-item, .wgcr-search-panel { transition: none !important; }
}
  • Keep contrast: the default colours were chosen for at least 4.5:1 contrast on the body text. If you recolour the panel, re-check contrast (see the accessibility page).
  • RTL and LTR: all layout rules use logical properties (inset-inline, margin-inline, border-inline-start), so the same CSS works in both directions. Avoid physical properties such as left or margin-right in your overrides.

Result structure

With the default template each result looks like this:

html
<article class="wgcr-search-item">
	<a class="wgcr-search-link" href="https://example.com/?p=42">
		<img class="wgcr-search-thumb" src="…" alt="Post title" width="72" height="72">
		<h4 class="wgcr-search-title">Post title</h4>
		<p class="wgcr-search-excerpt">A short text…</p>
		<time class="wgcr-search-date" datetime="2026-09-01">2026-09-01</time>
	</a>
</article>

In custom template mode the markup is whatever your Elementor template produces, and wgcr-search--tpl is added to the root so you can style it separately.

JavaScript configuration

Before search.js, a configuration object is printed inline:

js
window.WGCRSearch = {
	restUrl: 'https://example.com/wp-json/wgcr/v1/search',
	homeUrl: 'https://example.com/',
	minChars: 2,
	maxResults: 50,
	i18n: {
		noResults: 'No results found.',
		found: 'results',
		error: 'Error fetching results.',
		prev: 'Previous',
		next: 'Next',
		page: 'Page',
		pageOf: 'of'
	}
};
KeyMeaning
restUrlThe search route address, built with rest_url()
homeUrlThe site address, used by the real search form
minCharsMinimum query length before a request is sent (2)
maxResultsHard cap on items per page (50)
i18nInterface texts, translated into the site language

Client-side behaviour

BehaviourValue
Minimum query length2 characters (minChars)
Delay after typing (debounce)250 milliseconds
Results cap per request50 (WGCR_SEARCH_MAX_RESULTS)
Cancelling the previous requestWith AbortController while typing fast
Endpoint/wp-json/wgcr/v1/search
Infinite scrollWith IntersectionObserver on .wgcr-search-sentinel

The flow: the visitor types → 250 ms are waited → if the query is shorter than 2 characters the panel is emptied and cleared → otherwise the previous request is aborted and a new one is sent → results are rendered → the status message (“N results”) is read out to screen readers.

Working without JavaScript

The widget's form is a real GET form whose action is the site home, with an s field and a hidden post_type field. So if JavaScript is disabled, submitting the form takes the visitor to WordPress' own search results page for that post type. This is the pattern recommended for accessibility and SEO: progressive enhancement, not a hard dependency on a script.

Data attributes on the widget root

The JavaScript reads its settings from the data-* attributes on the root element, so if you build the widget with dynamic templates or PHP code, include all of them:

AttributeExample
data-wgcr-searchThe initialisation marker (no value)
data-display-modemodal or page
data-sourcepost, page, product, …
data-limit12
data-thumbyes / no
data-excerptyes / no
data-allThe text of the “show all” link
data-templateAn Elementor template id, or 0
data-queryCompact JSON with orderby, order, date, terms, terms_op, ignore_sticky, query_id
data-paginationnone, numbers, prev_next, load_on_click, load_on_scroll

Complete control reference

Every content control of the widget, in panel order. Style controls are listed in “Style sections” above.

IdPanel labelTypeDefaultShown when
display_modeResults display modeSelectmodal (List mode)—
placeholderPlaceholder textTextEmpty (filled automatically)—
thumbShow thumbnailSwitcherYes—
excerptShow excerptSwitcherYes—
all_text“Show all” textText“Show all results”—
template_modeTemplate choiceSelectdefault—
templateChoose a templateSelect with searchEmptytemplate_mode = custom
edit_template_linkEdit templateRaw HTML (link)—template_mode = custom
columnsColumnsResponsive number3 (tablet 2, mobile 1)display_mode = page
posts_per_pageItems per pageNumber, 1 to 5012—
masonryMasonry layoutSwitcherNodisplay_mode = page
equal_heightEqual heightSwitcherNodisplay_mode = page and masonry off
sourceSourceSelectpost—
custom_typeCustom post typeTextEmptysource = custom
include_byInclude bySelectexclude—
include_termsCategories and tagsTerm selectorEmpty—
date_filterDateSelectall—
orderbyOrder bySelectrelevance—
orderOrderSelectDESCorderby is not relevance or rand
ignore_stickyIgnore sticky postsSwitcherYessource = post
query_idQuery IDTextEmpty—
pagination_typePaginationSelectnone—
pagination_alignAlignmentResponsive selectcenterpagination_type is not none
pagination_spacerSpacerSelect (with/without)nopagination_type is not none
button_headingButtonSeparator heading—pagination_type = load_on_click
button_textButton textText“View more posts”pagination_type = load_on_click
button_iconButton iconSelect (show/hide)yespagination_type = load_on_click
button_idButton IDTextEmptypagination_type = load_on_click
no_posts_heading“No more posts” messageSeparator heading—load_on_click or load_on_scroll
no_posts_alignMessage alignmentResponsive selectcenterload_on_click or load_on_scroll
custom_messageCustom messageSwitcherNoload_on_click or load_on_scroll
custom_message_textCustom message textTextareaEmptyThe above plus custom_message = yes

Troubleshooting

SymptomLikely causeFix
Nothing appears while typingThe query is shorter than 2 charactersType two characters or more
“Error fetching results.”The REST API is blocked, or a server errorRun curl against /wp-json/wgcr/v1/search?q=test
Duplicate resultsTwo widgets sharing the same button idUse a unique id, or leave it empty
Result images are emptyThe post has no featured imageThe .wgcr-search-thumb--empty class fills the space; or turn “Show thumbnail” off
Columns have no effectThe display mode is List modeSwitch the mode to Page mode
Works in the editor but not on the sitePage cache or a JS optimiserExclude search.js from combining or delaying