Using Best Classifieds

Live Search

May 2, 2026 docsadmin 5 min read

Live Search

The hero search dropdown is one of the most-used parts of the site — a polished, debounced live-search experience with smart category icons. This page explains how it works, what’s customizable, and how to extend it.

Free feature — no Pro license required. Built into the base theme.


How it works

User types in hero search Debounced 200ms after last keystroke REST API call /wp-json/best-classifieds/v1/search?q=… WP_Query listings + categories Smart icon assigned category → keyword → fallback JSON response 8 listings + 4 categories Render dropdown highlight matches, pills Keyboard nav: ↓/↑ to walk results · Enter to open · Esc to close · Click outside to dismiss

The dropdown is a single-state machine — at any time, exactly one of loading, empty, or results is shown. This prevents the “spinner + empty state + results stacked” bug some search UIs have.


REST endpoint

GET /wp-json/best-classifieds/v1/search?q={query}

Param Type Required Description
q string Yes Search query, minimum 2 characters

Response

{
  "q": "honda",
  "listings": [
    {
      "id": 42,
      "title": "2018 Honda Civic Sport — One Owner",
      "url": "https://example.com/listing/honda-civic/",
      "icon": "ti-car",
      "excerpt": "Bought new from Austin Honda in 2018, single owner...",
      "location": "Austin, TX",
      "price": "$14,800"
    }
  ],
  "categories": [
    {
      "id": 12,
      "name": "Vehicles",
      "url": "https://example.com/category/vehicles/",
      "icon": "ti-car",
      "count": 8
    }
  ]
}

The endpoint is public (__return_true permission callback). No auth required.


Smart icon assignment

Each listing in the response gets an icon field — a Tabler icon class. The matching logic in best_classifieds_smart_icon_for_post():

  1. Category-based (highest priority): If the listing belongs to a category with a defined icon (Vehicles → ti-car), that’s the icon.
  2. Keyword scan: If no category icon was found, the title is scanned against a keyword map: “Honda” / “Civic” / “BMW” → ti-car, “MacBook” / “iPhone” → ti-device-laptop, etc.
  3. Generic fallback: ti-tag.

The keyword map is in inc/template-functions.php. To add your own keyword groups via a filter:

add_filter( 'best_classifieds_smart_icon_keywords', function( $map ) {
    $map['boats|yacht|sailboat|kayak'] = 'ti-sailboat';
    $map['camera|lens|tripod|gopro']   = 'ti-camera';
    return $map;
} );

Pattern: 'pattern1|pattern2|pattern3' => 'ti-iconname'. Patterns are word-boundary regex, case-insensitive.


Frontend behavior

Debouncing

The input listener has a 200ms debounce. Typing rapidly fires only one request after you pause. Set the timer if you want — search for debounceTimer = setTimeout(... 200); in live-search.js.

Stale response handling

If the user types faster than the network responds, an older request can return after a newer one. The JS guards against this:

if (input.value.trim() !== query) return; // stale response

The discarded response is silently dropped. Only the latest query’s results render.

Keyboard navigation

Key Action
/ Move active result up/down
Enter Open the active result; if none active, submit form normally (full search results page)
Esc Close the dropdown
Tab Standard browser tab navigation

Highlighting

The query is wrapped in <mark> tags around matches in the title and excerpt. The <mark> is styled in the accent color (gold by default) so matches pop visually.

The highlight respects HTML escaping — < in the title is escaped before the regex runs, so no XSS surface.


Customizing what shows up

Limit search to specific post types

By default the endpoint queries best_classifieds_get_listing_post_type() (which is post for the free theme, listing for Pro CPT mode). To search additional post types:

add_filter( 'best_classifieds_search_post_types', function( $types ) {
    return array( 'post', 'page', 'product' ); // include pages and WooCommerce products
} );

Change the result count

Default is 8 listings + 4 categories. To change:

add_filter( 'best_classifieds_search_listings_limit', function() { return 5; } );
add_filter( 'best_classifieds_search_categories_limit', function() { return 6; } );

Add custom fields to the response

The excerpt, location, and price fields are emitted; to add more (e.g. condition, mileage, salary range):

add_filter( 'best_classifieds_search_result_fields', function( $fields, $post_id ) {
    $fields['condition'] = get_post_meta( $post_id, '_bc_condition', true );
    return $fields;
}, 10, 2 );

The frontend JS doesn’t render these by default. To show them in the dropdown, you’d also extend live-search.js (or override it from a child theme).


Performance considerations

The endpoint runs a WP_Query with s={query} — which is WordPress’s default LIKE-based search. For sites with 10,000+ listings, you’ll want to plug in a better search engine:

  • ElasticPress — works automatically; it intercepts WP_Query and routes to Elasticsearch
  • SearchWP — paid plugin with great relevance tuning
  • Algolia — replace the endpoint entirely with an Algolia-powered alternative

To replace the entire endpoint with your own:

remove_action( 'rest_api_init', 'best_classifieds_register_search_endpoint' );

add_action( 'rest_api_init', function() {
    register_rest_route( 'best-classifieds/v1', '/search', array(
        'methods'             => 'GET',
        'permission_callback' => '__return_true',
        'callback'            => 'my_custom_search_callback',
    ) );
} );

Return the same JSON shape and the frontend JS will keep working unchanged.


If you want a plain old form post (no dropdown):

add_filter( 'best_classifieds_enable_live_search', '__return_false' );

The form still works — it just submits to the standard search page on Enter, no live results.


Mobile behavior

The dropdown adapts to the small viewport:

  • Full-width (extends past form edges via left: -8px; right: -8px)
  • Max height 65vh so it doesn’t crowd the keyboard
  • Excerpt is hidden (just title + meta to save space)
  • Touch targets minimum 44px

The search input is font-size: 16px to prevent iOS auto-zooming on focus — a small detail that makes the experience feel native.

Leave a comment