# Quick Search Widget (JS)

> Source: https://docs.recombee.com/widget-sdks/quick-search-widget-js

> For the complete documentation index, see [llms.txt](/llms.txt).

**Table of contents**

* [ Installation ](#install)
* [ Client Initialization ](#client-initialization)  
   * [ Providing User ID ](#providing-user-id)
* [ Basic Example ](#basic-example)
* [ Custom Template ](#custom-template)
* [ Multi-Type Search Results ](#multi-type-search-results)
* [API Reference](#api)  
   * [QuickSearchWidget](#api/QuickSearchWidget)  
   * [QuickSearchWidgetOptions](#api/QuickSearchWidgetOptions)  
   * [QuickSearchWidgetProps](#api/QuickSearchWidgetProps)  
   * [QuickSearchWidgetState](#api/QuickSearchWidgetState)  
   * [ItemImage](#api/ItemImage)  
   * [ItemImageProps](#api/ItemImageProps)  
   * [addRecommIdQueryParam](#api/addRecommIdQueryParam)

# 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.2.16 and the API can change. Please specify exact version when installing.

## Installation

Install `@recombee/quick-search-widget-js@0.2.16` 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.2.16 recombee-js-api-client

```

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

## Client Initialization

The widget loads recommendation data using [Recombee API Client](/js_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 (, , {
  : ,
});

```

The Database Public Token can be found in the Admin UI[Database Settings Page](https://admin.recombee.com/go-to-database/settings).

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](https://admin.recombee.com/go-to-database/scenarios).

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

```

### Providing User ID

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

```

## Basic Example

The widget in this example uses the [DefaultItem](#api/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](/api#recommend-items-to-user-param-returnProperties) is set in the request, and optionally use[includedProperties](/api#recommend-items-to-user-param-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>`,
});

```

## Custom Template

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

* [DropdownComponent](#api/DropdownComponent) – Renders the dropdown containing search results.
* [InputComponent](#api/InputComponent) – Handles the search input field where users type their queries.
* [TriggerComponent](#api/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>`,
});

```

## Multi-Type Search Results

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](/api#search-item-segments) requests.

All search requests are sent together in a single [Batch](/api#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>`,
});

```

## API Reference

const

### QuickSearchWidget

Quick Search widget

type

### QuickSearchWidgetOptions

Quick Search widget options

#### Properties

##### container

string

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

---

##### apiClient

ApiClient

Instance of Recombee JS API Client. See[Example](#client-initialization).

---

##### createRequest

QuickSearchCreateRequestFunction

Request factory function. See[Quick Example](#client-initialization) or visit[API Reference](/api) for overview of available requests.

---

##### onRecommResponse

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

```

---

##### initialQuery

string | undefined

String to be prefilled into the search input

---

##### minSearchCharactersCount

number

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

---

##### typingDebounceDuration

number | undefined

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

---

##### primaryResultsIndex

number | undefined

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

---

##### InputComponent

FC<InputProps>

Component responsible for rendering the widget input.

---

##### TriggerComponent

FC<TriggerProps>

Component responsible for rendering the widget trigger on mobile devices.

---

##### DropdownComponent

FC<DropdownProps>

Component responsible for rendering the widget dropdown.

---

##### className

string | undefined

Custom classes of widget wrapper element. See[Custom CSS](#custom-css).

---

##### popoverClassName

string | undefined

Custom classes of popover wrapper element. See[Custom CSS](#custom-css).

---

##### popoverClassNameDisableDefault

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](#custom-css).

---

##### desktopMediaQuery

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

### QuickSearchWidgetProps

Quick Search component configuration options

#### Properties

##### apiClient

ApiClient

Instance of Recombee JS API Client. See[Example](#client-initialization).

---

##### createRequest

QuickSearchCreateRequestFunction

Request factory function. See[Quick Example](#client-initialization) or visit[API Reference](/api) for overview of available requests.

---

##### onRecommResponse

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

```

---

##### initialQuery

string | undefined

String to be prefilled into the search input

---

##### minSearchCharactersCount

number

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

---

##### typingDebounceDuration

number | undefined

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

---

##### primaryResultsIndex

number | undefined

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

---

##### InputComponent

FC<InputProps>

Component responsible for rendering the widget input.

---

##### TriggerComponent

FC<TriggerProps>

Component responsible for rendering the widget trigger on mobile devices.

---

##### DropdownComponent

FC<DropdownProps>

Component responsible for rendering the widget dropdown.

---

##### className

string | undefined

Custom classes of widget wrapper element. See[Custom CSS](#custom-css).

---

##### popoverClassName

string | undefined

Custom classes of popover wrapper element. See[Custom CSS](#custom-css).

---

##### popoverClassNameDisableDefault

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](#custom-css).

---

##### desktopMediaQuery

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

### QuickSearchWidgetState

Class exposing quick search widget state for use in custom templates

#### Properties

##### isDesktop

boolean

Indicates that the widget is displayed on desktop device.

---

##### reset

() => void

Resets the widget to its initial state

---

##### inputProps

{ 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.

---

##### triggerProps

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

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

---

##### closeButtonProps

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

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

---

##### items

(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.

---

##### isExpanded

boolean

Boolean flag indicating that the widget dropdown is open.

---

##### isLoading

boolean

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

---

const

### ItemImage

Item image component

type

### ItemImageProps

Item Image properties

#### Properties

##### src

string | null | undefined

Image URL

---

##### missingImageSrc

string | undefined

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

---

##### className

string | undefined

Image wrapper class name

---

##### imgClassName

string | undefined

Image class name

---

##### width

number | undefined

Image width in pixels

---

##### height

number | undefined

Image height in pixels

---

##### verticalAlignment

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

Image vertical alignment

---

##### horizontalAlignment

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

Image horizontal alignment

---

##### fittingAlgorithm

"cover" | "scale\_down" | undefined

Image fitting algorithm

---

function

### addRecommIdQueryParam

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