Recombee Docs
Visit recombee.comStart Free
docs20User Documentation
adminuiAdmin UI
reql32ReQL
codeAPI Clients & Integrations
cookhatScenario Recipes
suitcaseMisc
Widget SDKs / Quick Search / Search Quick Widget (JS)

Quick Search Widget (JS)

A Vanilla JS widget that enables users to perform real-time product searches with instant results, enhancing product discoverability and conversion.

Use this library when you want to display a widget without using a transpiler like Babel and without using JSX. This library exports a htm HTML factory to assemble custom elements of the widget.

This widget library is version 0.1.18 and the API can change. Please specify exact version when installing.

Install @recombee/quick-search-widget-js@0.1.18 and recombee-js-api-client packages using your preferred NPM package manager. This example is using pnpm.

pnpm add @recombee/quick-search-widget-js@0.1.18 recombee-js-api-client
Copy

Always remember to apply the default CSS file distributed alongside the widget library, as shown in the examples.

The widget loads recommendation data using Recombee API Client. Here is how to initialize the client with necessary configuration for a specific database:

import {  } from "recombee-js-api-client";

const  = "[database-id]";
const  = "[database-public-token]";
const  = "[database-region]";

export const  = new (, , {
  : ,
});
Copy

The Database Public Token can be found in the Admin UI Database Settings Page.

The widget also needs to be provided a createRequest function, which instantiates a client request class to define which data to pull from the database. Use Scenario ID which can be found on Admin GUI Database Scenarios Page.

Quick Search widget requires the createRequest function to return a Batch Request. For a basic case of single search request, always wrap it in a batch.

import { type  } from "@recombee/quick-search-widget-react";
import { , ,  } from "recombee-js-api-client";

const :  = ({  }) => {
  return new (
    [
      new (userId, , 5, {
        : "search-items",
        : true,
        : true,
      }),
      new (userId, , 5, {
        : "search-categories",
        : true,
      }),
    ],
    {
      : true,
    },
  );
};
Copy

Each visitor of your website should be identified by a user identificator (userId) to correlate user activity and deliver best possible recommendation performance. The userId should preferrably originate from your user's account details when the user is authenticated or as some session-persistent random ID when they are anonymous. The SDK provides utility which generates random user id and saves it to a cookie to cover the latter case:

import { type  } from "@recombee/quick-search-widget-react";
import { , ,  } from "recombee-js-api-client";
import {  } from "@recombee/carousel-widget-react";

let : string | undefined;
if (authenticatedUserId) {
   = authenticatedUserId;
} else {
   = .();
}

const :  = ({  }) => {
  return new (
    [
      new (, , 5, {
        : "search-items",
        : true,
        : true,
      }),
      new (, , 5, {
        : "search-categories",
        : true,
      }),
    ],
    {
      : true,
    },
  );
};
Copy

The widget in this example uses the DefaultItem component to render each recommendation in a consistent layout.

The resulting widget is inserted into the element specified by the container field.

Values of the recommended items - such as title, image URL, or link URL - are obtained from the API response and accessed via props.result?.values.

Ensure that returnProperties: true is set in the request, and optionally use includedProperties to control which item properties are returned.

import {
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
} from "@recombee/quick-search-widget-js";

import "@recombee/quick-search-widget-js/dist/styles.css";

({
  : "#widget-root",
  : ,
  : ,
  : 3,
  : "(min-width: 1000px)",
  : "lg:w-[var(--qs-input-width)] lg:flex lg:justify-center",

  : () =>
    `<form className="flex w-[400px] text-[#374040]">
      <${}
        state=${.}
        inputProps=${{
          : `Search for "table"...`,
        }}
      />
    </form>`,

  : () =>
    `<button
      ...${..}
      className="flex size-[38px] items-center justify-center rounded-sm bg-[#3bc4a1] text-white"
    >
      <${} />
    </button>`,

  : () =>
    `<div
      className="mt-1 flex h-full max-h-full min-h-0 flex-col rounded-sm bg-white text-[#374040] shadow-2xl lg:h-auto lg:max-w-[900px] lg:min-w-[600px]"
    >
      ${!.. &&
      `<form className="flex items-center gap-2 p-2">
        <div className="flex-grow">
          <${}
            state=${.}
            inputProps=${{
              : `Search for "table"...`,
            }}
          />
        </div>
        <button
          ...${..}
          className="flex size-[38px] items-center justify-center"
        >
          <${} />
        </button>
      </form>`}
      <div className="flex min-h-0 flex-grow flex-col">
        <div className="px-4 py-4 pb-2 text-sm font-semibold text-[#3f91ff]">
          Results
        </div>
        <div className="overflow-auto p-2">
          ${.
            .(0)
            .(
              () =>
                `<${}
                  key=${.}
                  href=${(
                    `${.?.}`,
                    .,
                  )}
                  image=${`<${}
                    className="size-16 overflow-hidden rounded-lg"
                    src=${`${.?.}`}
                    width=${600}
                    height=${400}
                  />`}
                  primaryContent=${.?.}
                  secondaryContent=${.?.}
                  highlightedContent=${`USD ${.?.}`}
                  ...${.}
                />`,
            )}
        </div>
      </div>
    </div>`,
});
Copy

You can customize the appearance and behavior of the Quick Search Widget by overriding its individual components:

  • DropdownComponent – Renders the dropdown containing search results.
  • InputComponent – Handles the search input field where users type their queries.
  • TriggerComponent – Controls how the widget is opened on mobile devices.

The example also demonstrates how to make the search form submittable to a dedicated results page using a submit button.

import {
  ,
  ,
  ,
  ,
  ,
  ,
  ,
} from "@recombee/quick-search-widget-js";

import "@recombee/quick-search-widget-js/dist/styles.css";

({
  : "#widget-root",
  : ,
  : ,
  : 3,
  : 1000,
  : "(min-width: 1000px)",
  : () =>
    `<form
      method="GET"
      action="https://example.com/search"
      target="_blank"
      className="flex w-[400px] gap-2 text-[#374040]"
    >
      <div
        className="relative flex flex-grow overflow-hidden rounded-lg border border-[#d9dbdb]"
      >
        <div
          className="absolute flex size-[38px] items-center justify-center text-[#737979]"
        >
          <${} />
        </div>
        <input
          name="q"
          type="text"
          className="block h-[39px] w-full indent-[38px] outline-hidden"
          placeholder=${`Search for "table"...`}
          ...${..}
        />${.. &&
        `<${}
          className="rb:absolute rb:top-2.25 rb:right-2.25 rb:h-5 rb:w-5"
        />`}
      </div>
      <button
        type="submit"
        className="rounded-lg bg-[#3f91ff] px-5 py-2.5 text-sm font-medium text-white focus:ring-4 focus:ring-blue-300 focus:outline-hidden"
      >
        Search
      </button>
    </form>`,
  : () =>
    `<button
      ...${..}
      className="flex size-[38px] items-center justify-center rounded-sm bg-[#3bc4a1] text-white"
    >
      <${} />
    </button>`,

  : () =>
    `<div
      className="mt-1 flex h-full max-h-full min-h-0 flex-col rounded-sm bg-white text-[#374040] shadow-2xl lg:h-auto lg:max-w-[900px] lg:min-w-[600px]"
    >
      ${!.. &&
      `<form className="flex items-center gap-2 p-2">
        <div className="flex-grow">
          <div
            className="relative flex flex-grow overflow-hidden rounded-lg border border-[#d9dbdb]"
          >
            <div
              className="flex size-[42px] items-center justify-center text-[#737979]"
            >
              <${} />
            </div>
            <input
              type="text"
              className="block w-full outline-hidden"
              placeholder="Search in docs..."
              ...${..}
            />${.. &&
            `<div
              className="absolute top-2.5 right-2.5 h-5 w-5 animate-spin rounded-full border-2 border-dashed border-current text-[#d9dbdb] [--animate-spin:spin_3s_linear_infinite]"
            />`}
          </div>
        </div>
        <button
          ...${..}
          className="flex size-[38px] items-center justify-center"
        >
          <${} />
        </button>
      </form>`}
      <div className="flex min-h-0 flex-grow flex-col">
        <div className="px-4 py-4 pb-2 text-sm font-semibold text-[#3f91ff]">
          Results
        </div>
        <div className="overflow-auto p-2">
          ${..(0).(
            () =>
              `<a
                key=${.}
                href=${(
                  `${.?.}`,
                  .,
                )}
                ...${.}
                className="flex items-center gap-4 rounded-sm p-2 outline-hidden hover:bg-[#f7f7f7] focus:bg-[#f7f7f7]"
                ><div>
                  <${}
                    className="size-16 overflow-hidden rounded-lg"
                    src=${`${.?.}`}
                    width=${600}
                    height=${400}
                  />
                </div>
                <div className="rb:flex-grow">
                  <div
                    className="rb:font-semibold rb:text-nowrap rb:text-ellipsis rb:overflow-hidden"
                  >
                    ${.?.}
                  </div>
                  <div
                    className="rb:text-[#737979] rb:text-nowrap rb:text-ellipsis rb:overflow-hidden"
                  >
                    ${.?.}
                  </div>
                </div>
                <div
                  className="rb:font-bold rb:text-[#3f91ff] rb:text-nowrap rb:text-ellipsis rb:overflow-hidden"
                >
                  ${`USD ${.?.}`}
                </div></a
              >`,
          )}
        </div>
      </div>
    </div>`,
});
Copy

You can use the Quick Search Widget to display multiple types of results. For example, showing not only items but also categories or brands using the Search Item Segments requests.

All search requests are sent together in a single Batch request.

import {
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
} from "@recombee/quick-search-widget-js";

import "@recombee/quick-search-widget-js/dist/styles.css";

const :  = ({  }) => {
  return new (
    [
      new ("userId", , 5, {
        : "search",
        : true,
        : true,
      }),
      new ("userId", , 5, {
        : "search-brands",
        : true,
      }),
    ],
    {
      : true,
    },
  );
};

({
  : "#widget-root",
  : ,
  : ,
  : 3,
  : 0,
  : "(min-width: 1000px)",
  : "lg:w-[var(--qs-input-width)] lg:flex lg:justify-center",

  : () =>
    `<form className="flex w-[400px] text-[#374040]">
      <${}
        state=${.}
        inputProps=${{
          : `Search for "table"...`,
        }}
      />
    </form>`,

  : () =>
    `<button
      ...${..}
      className="flex size-[38px] items-center justify-center rounded-sm bg-[#3bc4a1] text-white"
    >
      <${} />
    </button>`,

  : () =>
    `<div
      className="mt-1 flex h-full max-h-full min-h-0 flex-col rounded-sm bg-white text-[#374040] shadow-2xl lg:h-auto lg:max-w-[900px] lg:min-w-[600px]"
    >
      ${!.. &&
      `<form className="flex items-center gap-2 p-2">
        <div className="flex-grow">
          <${}
            state=${.}
            inputProps=${{
              : `Search for "table"...`,
            }}
          />
        </div>
        <button
          ...${..}
          className="flex size-[38px] items-center justify-center"
        >
          <${} />
        </button>
      </form>`}
      <div className="flex">
        <div className="flex min-h-0 flex-col">
          <div className="px-4 py-4 pb-2 text-sm font-semibold text-[#3f91ff]">
            Segments
          </div>
          <div className="overflow-auto p-2">
            ${..(1).(
              () =>
                `<a
                  key=${.}
                  href=${(
                    `${.?.}`,
                    .,
                  )}
                  ...${.}
                  className="flex items-center gap-2 rounded-sm p-2 text-nowrap outline-hidden hover:bg-[#f7f7f7] focus:bg-[#f7f7f7]"
                  ><div className="flex-grow">
                    <div className="font-semibold">${.}</div>
                  </div></a
                >`,
            )}
          </div>
        </div>
        <div className="flex min-h-0 min-w-0 flex-grow flex-col">
          <div className="px-4 py-4 pb-2 text-sm font-semibold text-[#3f91ff]">
            Results
          </div>
          <div className="overflow-y-auto p-2">
            ${.
              .(0)
              .(
                () =>
                  `<${}
                    key=${.}
                    href=${(
                      `${.?.}`,
                      .,
                    )}
                    image=${`<${}
                      className="size-16 overflow-hidden rounded-lg"
                      src=${`${.?.}`}
                      width=${600}
                      height=${400}
                    />`}
                    primaryContent=${.?.}
                    secondaryContent=${.?.}
                    highlightedContent=${`USD ${.?.}`}
                    ...${.}
                  />`,
              )}
          </div>
        </div>
      </div>
    </div>`,
});
Copy

API Reference

const

Quick Search widget

type

Quick Search widget options

Properties

string

CSS Selector to target the element where the widget should be inserted.


ApiClient

Instance of Recombee JS API Client. See Example.


QuickSearchCreateRequestFunction

Request factory function. See Quick Example or visit API Reference for overview of available requests.


WidgetRecommResponseCallback | undefined

Callback function allowing to intercept and inspect recommendation request+response made by widget.

import React from "react";
import {  } from "@recombee/carousel-widget-js";

({
  // ...ommited code...
  : ({ ,  }) => {
    // use data from request and response for any purpose, i.e. internal tracking
  },
});
Copy

string | undefined

String to be prefilled into the search input


number

Minimum length of search query for search request to be sent.


number | undefined

Maximum duration between keystrokes in milliseconds before search request is made.


number | undefined

Index of request in a batch from which the results are considered to be navigable by arrow keys.


FC<InputProps>

Component responsible for rendering the widget input.


FC<TriggerProps>

Component responsible for rendering the widget trigger on mobile devices.


FC<DropdownProps>

Component responsible for rendering the widget dropdown.


string | undefined

Custom classes of widget wrapper element. See Custom CSS.


string | undefined

Custom classes of popover wrapper element. See Custom CSS.


boolean | undefined

Disables default classes of popover wrapper element.

There are some default class names with essential styles applied to the popover wrapper element. This setting disables them as an escape hatch for customization. See Custom CSS.


string

CSS media query specifing when the widget should behave as displayed on desktop. By default, widget behaves as mobile-first, filling entire device screen with results dropdown.


type

Quick Search component configuration options

Properties

ApiClient

Instance of Recombee JS API Client. See Example.


QuickSearchCreateRequestFunction

Request factory function. See Quick Example or visit API Reference for overview of available requests.


WidgetRecommResponseCallback | undefined

Callback function allowing to intercept and inspect recommendation request+response made by widget.

import React from "react";
import {  } from "@recombee/carousel-widget-js";

({
  // ...ommited code...
  : ({ ,  }) => {
    // use data from request and response for any purpose, i.e. internal tracking
  },
});
Copy

string | undefined

String to be prefilled into the search input


number

Minimum length of search query for search request to be sent.


number | undefined

Maximum duration between keystrokes in milliseconds before search request is made.


number | undefined

Index of request in a batch from which the results are considered to be navigable by arrow keys.


FC<InputProps>

Component responsible for rendering the widget input.


FC<TriggerProps>

Component responsible for rendering the widget trigger on mobile devices.


FC<DropdownProps>

Component responsible for rendering the widget dropdown.


string | undefined

Custom classes of widget wrapper element. See Custom CSS.


string | undefined

Custom classes of popover wrapper element. See Custom CSS.


boolean | undefined

Disables default classes of popover wrapper element.

There are some default class names with essential styles applied to the popover wrapper element. This setting disables them as an escape hatch for customization. See Custom CSS.


string

CSS media query specifing when the widget should behave as displayed on desktop. By default, widget behaves as mobile-first, filling entire device screen with results dropdown.


class

Class exposing quick search widget state for use in custom templates

Properties

boolean

Indicates that the widget is displayed on desktop device.


() => void

Resets the widget to its initial state


{ value: string; onChange: (event: ChangeEvent<HTMLInputElement>) => void; onKeyDown: (event: KeyboardEvent<HTMLInputElement | HTMLAnchorElement | HTMLDivElement>) => void; onClick: () => void; ref: (element: HTMLElement | null) => void; }

Properties to be passed to a input component.


{ ref: (element: HTMLElement | null) => void; onClick: () => void; }

Properties to be passed to the trigger button of a mobile widget.


{ type: "button"; onClick: () => void; onTouchEnd: () => void; }

Properties to be passed to the close button of a mobile dropdown.


(index: number) => { key: string; id: string; recommId: string; values: { [key: string]: any; }; metadata: {} | undefined; itemProps: { tabIndex: number; "data-search-result-id"?: string | undefined; onKeyDown: (event: KeyboardEvent<HTMLInputElement | HTMLAnchorElement | HTMLDivElement>) => void; }; }[]

Getter for current results of the request batch. The index parameter indicates an index of results in the Batch Request for which to return items.


boolean

Boolean flag indicating that the widget dropdown is open.


boolean

Boolean flag indicating that a request for new results is in flight.


const

Item image component

type

Item Image properties

Properties

string | null | undefined

Image URL


string | undefined

URL of an image to display when the main image could not be loaded.


string | undefined

Image wrapper class name


string | undefined

Image class name


number | undefined

Image width in pixels


number | undefined

Image height in pixels


"top" | "center" | "bottom" | undefined

Image vertical alignment


"center" | "left" | "right" | undefined

Image horizontal alignment


"cover" | "scale_down" | undefined

Image fitting algorithm


function

Utility function to append recombee_recomm_id parameter to an url string. For usage in widget template to construct item link URL.

© Copyright 2025, Recombee s.r.o
docs.recombee.com