> ## Documentation Index
> Fetch the complete documentation index at: https://www.algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# compareBar

> Shows selected products and sends the selected records to the chat widget for an agent to compare.

```ts Signature theme={"system"}
compareBar({
  container: string | HTMLElement,
  configurationId: string,
  // Optional parameters
  minItems?: number,
  maxItems?: number,
  transformItems?: function,
  templates?: object,
  cssClasses?: object,
  translations?: object,
});
```

## Import

```js Package manager theme={"system"}
import { compareBar } from 'instantsearch.js/es/widgets';
```

```js CDN theme={"system"}
const { compareBar } = instantsearch.widgets;
```

## About this widget

Use the `compareBar` widget to let users compare products with an [Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio) agent.
The widget renders a comparison bar with the products users selected for comparison and a **Compare** button.
When users select **Compare**, the widget opens the [`chat`](https://www.algolia.com/doc/api-reference/widgets/chat/js) widget on the same index and sends a comparison request with the selected records attached.
The widget clears the selection after sending the request.
The chat displays the agent's comparison of the attached records without showing the request as a user message.

The widget doesn't include controls for selecting products.
Add a **Compare** checkbox to each product in your [`hits`](https://www.algolia.com/doc/api-reference/widgets/hits/js) item template.
Use `search.renderState.<indexName>.compare` to read and update the shared selection, as shown in the [example](#examples).
To build a custom comparison bar or **Compare** button, use the [`connectCompare`](#customize-the-ui-with-connectcompare) connector.
All comparison widgets on the same InstantSearch instance share the same selection.

The comparison bar stays hidden while the selection is empty.
Because `compareBar` opens the `chat` widget,
you don't need a [`chatTrigger`](https://www.algolia.com/doc/api-reference/widgets/chat-trigger/js) widget.

The widget requires a comparison configuration, which controls the comparison goal and instructions.
Create it in the Agent Studio dashboard and pass its ID as [`configurationId`](#param-configuration-id).

See also: [Compare products in chat](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/product-comparison.md)

## Examples

```js JavaScript icon=code expandable theme={"system"}
import instantsearch from 'instantsearch.js';
import { chat, compareBar, hits, searchBox } from 'instantsearch.js/es/widgets';
import 'instantsearch.css/components/chat.css';

const search = instantsearch({ indexName: 'instant_search', searchClient });

search.addWidgets([
  searchBox({ container: '#searchbox' }),
  hits({
    container: '#hits',
    templates: {
      item(hit, { html }) {
        // The selection `compareBar` owns. It's undefined until the bar mounts.
        const { isSelected, toggleItem, canAddItems } =
          search.renderState.instant_search?.compare ?? {};
        const selected = isSelected?.(hit.objectID) ?? false;

        return html`
          <article>
            <h2>${hit.name}</h2>
            <label>
              <input
                type="checkbox"
                checked=${selected}
                disabled=${!toggleItem || (!selected && !canAddItems)}
                onChange=${() => toggleItem?.(hit)}
              />
              Compare
            </label>
          </article>
        `;
      },
    },
  }),
  compareBar({
    container: '#compare-bar',
    minItems: 2,
    maxItems: 3,
    configurationId: 'CONFIGURATION_ID',
  }),
  chat({
    container: '#chat',
    agentId: 'AGENT_ID',
  }),
]);

search.start();
```

## Options

**container** `string | HTMLElement` *required*

The CSS selector or `HTMLElement` that identifies the widget's container.

```js string theme={"system"}
compareBar({
  container: '#compare-bar',
});
```

```js HTMLElement theme={"system"}
compareBar({
  container: document.querySelector('#compare-bar'),
});
```

**configurationId** `string` *required*

The ID of the comparison configuration that controls how the agent compares products.
Create comparison configurations in the **Components** section of your agent in the Agent Studio dashboard.
For more information, see [Compare products in chat](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/product-comparison.md).

The widget sends `__ALGOLIA_COMPARISON_<configurationId>__` as the user message,
with `comparison_configuration_id` and `selected_products` as [per-turn context](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/turn-context.md).
`selected_products` holds the selected records as a JSON string, with internal attributes such as `_highlightResult` removed.

Agent Studio replaces the placeholder with the configured agent instructions, and the chat doesn't show a user message for it.
If the configuration doesn't exist or is turned off,
the placeholder reaches the agent unchanged, so only set the ID of an enabled configuration.

```js JavaScript icon=code theme={"system"}
compareBar({
  // ...
  configurationId: 'CONFIGURATION_ID',
});
```

**minItems** `number` (default: 2)

The minimum number of products users must select before they can compare.
The **Compare** button remains unavailable until users select this number of products.
The value must be at least `2`.

Set `minItems` to the **Minimum products** value in your comparison configuration.

```js JavaScript icon=code theme={"system"}
compareBar({
  // ...
  minItems: 2,
});
```

**maxItems** `number` (default: 3)

The maximum number of products users can select.
Users can't add products after reaching this limit, and the bar shows a hint.
The value must be greater than or equal to [`minItems`](#param-min-items).

Set `maxItems` to the **Maximum products** value in your comparison configuration.

```js JavaScript icon=code theme={"system"}
compareBar({
  // ...
  maxItems: 4,
});
```

**transformItems** `function`

A function that receives the selected records and returns the records to display in the comparison bar.
The widget sends these transformed records to the agent when users select **Compare**.
Use this function to remove long attributes, such as product descriptions.

Return one record for each selected record and preserve its `objectID`.
The selection itself, including `isSelected` and the limits, uses the original records.

```js JavaScript icon=code theme={"system"}
compareBar({
  // ...
  transformItems(items) {
    return items.map(({ description, ...item }) => item);
  },
});
```

**templates** `object`

The [templates](#templates) to use for the widget.

```js JavaScript icon=code theme={"system"}
compareBar({
  // ...
  templates: {
    // ...
  },
});
```

**cssClasses** `object`

The [CSS classes you can override](https://www.algolia.com/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js.md#style-your-widgets):

* `root`. The root element of the widget.
* `header`. The header element.
* `title`. The title element.
* `count`. The selection count element.
* `list`. The list of selected items.
* `item`. Each selected item.
* `itemLabel`. The label of each selected item.
* `itemRemove`. The button that removes an item from the selection.
* `hint`. The hint shown when users select fewer than `minItems` or reach `maxItems`.
* `actions`. The container of the action buttons.
* `clearButton`. The button that clears the selection.
* `compareButton`. The button that starts the comparison.

```js JavaScript icon=code theme={"system"}
compareBar({
  // ...
  cssClasses: {
    root: 'MyCustomCompareBar',
    compareButton: [
      'MyCustomCompareBarButton',
      'MyCustomCompareBarButton--subclass',
    ],
  },
});
```

**translations** `object`

A dictionary of translations to customize the UI text and support internationalization.

* `title` (`string`). The title of the comparison bar. The default is `Compare`.
* `countText` (`(count: number, maxItems: number) => string`). The selection count. The default is `${count} of ${maxItems}`.
* `compareButtonText` (`(count: number) => string`). The label of the **Compare** button. The default is `Compare ${count}`.
* `clearButtonText` (`string`). The label of the **Clear all** button. The default is `Clear all`.
* `removeItemLabel` (`(itemLabel: string) => string`). The accessible label of each remove button. The default is `Remove ${itemLabel} from comparison`.
* `minItemsHint` (`(minItems: number) => string`). The hint shown while the selection is below `minItems`. The default is `Select at least ${minItems} products to compare.`.
* `limitReachedHint` (`string`). The hint shown when the selection reaches `maxItems`. The default is `Compare limit reached — remove a product to add another.`.

```js JavaScript icon=code theme={"system"}
compareBar({
  // ...
  translations: {
    title: 'Your comparison',
    compareButtonText: (count) => `Compare ${count} products`,
  },
});
```

## Templates

You can customize parts of a widget's UI using the Templates API.

Each template includes an `html` function,
which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates).
This function safely renders templates as HTML strings and works directly in the browser—no build step required.
For details, see [Templating your UI](https://www.algolia.com/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js.md#templating-your-ui).

> [!NOTE]
>
> The `html` function is available in InstantSearch.js version 4.46.0 or later.

**item** `function`

A template to replace the label of each selected item.
It receives the selected record as `item`.

The label uses `name` or `title`, falling back to `objectID`.
Each record must have an `objectID`.

```js JavaScript icon=code theme={"system"}
compareBar({
  // ...
  templates: {
    item({ item }, { html }) {
      return html`
        <img src="${item.image}" alt="" width="24" height="24" />
        <span>${item.name}</span>
      `;
    },
  },
});
```

## HTML output

```html HTML icon=code-xml expandable theme={"system"}
<section class="ais-CompareBar" aria-label="Compare">
  <div class="ais-CompareBar-header">
    <h3 class="ais-CompareBar-title">Compare</h3>
    <span class="ais-CompareBar-count" aria-live="polite">2 of 3</span>
  </div>
  <ul class="ais-CompareBar-list">
    <li class="ais-CompareBar-item" data-object-id="1">
      <span class="ais-CompareBar-itemLabel">Trail Jacket</span>
      <button class="ais-CompareBar-itemRemove" aria-label="Remove Trail Jacket from comparison">
        ...
      </button>
    </li>
    <li class="ais-CompareBar-item" data-object-id="2">
      <span class="ais-CompareBar-itemLabel">Storm Shell</span>
      <button class="ais-CompareBar-itemRemove" aria-label="Remove Storm Shell from comparison">
        ...
      </button>
    </li>
  </ul>
  <div class="ais-CompareBar-actions">
    <button class="ais-Button ais-Button--outline ais-Button--sm ais-CompareBar-clearButton">
      Clear all
    </button>
    <button class="ais-Button ais-Button--primary ais-Button--sm ais-CompareBar-compareButton">
      Compare 2
    </button>
  </div>
</section>
```

The widget doesn't render anything if the selection is empty.
Below `minItems` or at `maxItems`, it adds a `div.ais-CompareBar-hint` element before the actions.

The `instantsearch.css` chat theme anchors the bar to the bottom center of the viewport.
Import `instantsearch.css/components/chat.css` to style it.

## Customize the UI with `connectCompare`

To create a custom UI for the `compareBar` widget, use `connectCompare`.

Use it for a custom comparison bar, or for a **Compare** button outside the bar, such as in a dialog.
Every `compare` widget on the same InstantSearch instance reads and writes the same selection.
For a toggle on every hit, read the selection from `search.renderState.<indexName>.compare` in the `hits` item template instead of mounting a widget per hit, as in the [example](#examples).

Import `connectCompare` based on how you installed InstantSearch.js.

```js Package manager theme={"system"}
import { connectCompare } from 'instantsearch.js/es/connectors';
```

```js CDN theme={"system"}
const { connectCompare } = instantsearch.connectors;
// or directly use instantsearch.connectors.connectCompare()
```

Then complete these three steps:

```js JavaScript icon=code theme={"system"}
// 1. Create a render function
const renderCompare = (renderOptions, isFirstRender) => {
  // Rendering logic
};

// 2. Create the custom widget
const customCompare = connectCompare(renderCompare);

// 3. Instantiate
search.addWidgets([
  customCompare({
    // instance params
  }),
]);
```

### Create a render function

InstantSearch calls this rendering function before the first search (`init` lifecycle step),
each time results return from Algolia (`render` lifecycle step),
and each time the selection changes.

```js JavaScript icon=code theme={"system"}
const renderCompare = (renderOptions, isFirstRender) => {
  const {
    items,
    isSelected,
    toggleItem,
    removeItem,
    clearItems,
    canAddItems,
    canCompare,
    minItems,
    maxItems,
    compare,
    widgetParams,
  } = renderOptions;

  if (isFirstRender) {
    // Do some initial rendering and bind events
  }

  // Update the rendering with the new state
};
```

#### Render options

**items** `object[]`

The selected records, in selection order, after `transformItems`.
Each item has at least an `objectID`.

**isSelected** `(objectID: string) =\> boolean`

Whether the record with this `objectID` is in the selection.

**addItem** `(item: object) =\> void`

Adds a record to the selection.
It doesn't add the record if it's already selected or the selection has reached `maxItems`.
The record needs an `objectID`, and a `name` or `title` to label it.

**removeItem** `(objectID: string) =\> void`

Removes the record with this `objectID` from the selection.

**toggleItem** `(item: object) =\> void`

Adds the record to the selection when it isn't selected, and removes it otherwise.

**clearItems** `() =\> void`

Empties the selection.

**canAddItems** `boolean`

Whether the selection is below `maxItems`.

**canCompare** `boolean`

Whether the selection has reached `minItems`.

**minItems** `number`

The resolved [`minItems`](#param-min-items) value.

**maxItems** `number`

The resolved [`maxItems`](#param-max-items) value.

**compare** `() =\> boolean`

Opens the `chat` widget on the same index, sends the compare request with the selected records attached, and clears the selection.
Returns `true` when it submits the message and `false` when the selection is below `minItems`,
the page doesn't have a `chat` widget, or the chat is busy.

**widgetParams** `object`

All original widget options forwarded to the render function.

### Create and instantiate the custom widget

Create a custom widget from the rendering function, then instantiate it.
You can pass two types of parameters:

* **Instance parameters**: predefined options that configure the widget.
* **Custom parameters**: parameters you define for the custom widget.

Both instance and custom parameters are available through `renderOptions.widgetParams` in the render function.

```js JavaScript icon=code theme={"system"}
const customCompare = connectCompare(renderCompare);

search.addWidgets([
  customCompare({
    minItems: 2,
    maxItems: 3,
    configurationId: 'CONFIGURATION_ID',
  }),
]);
```

#### Instance options

The connector accepts the same options as the widget: [`configurationId`](#param-configuration-id) (required), [`minItems`](#param-min-items), [`maxItems`](#param-max-items), and [`transformItems`](#param-transform-items).
Pass the same values to every `compare` widget so they use the same configuration and limits.

### Full example

This example adds a **Compare** checkbox to each hit and renders a minimal bar.
The hits template reads the selection the bar owns, so checking a hit updates the bar.

```js JavaScript icon=code expandable theme={"system"}
import instantsearch from 'instantsearch.js';
import { connectCompare } from 'instantsearch.js/es/connectors';
import { chat, hits, searchBox } from 'instantsearch.js/es/widgets';

const search = instantsearch({ indexName: 'instant_search', searchClient });

const compareBarCustom = connectCompare((renderOptions, isFirstRender) => {
  const { items, canCompare, compare, clearItems } = renderOptions;
  const container = document.querySelector('#compare-bar');

  if (isFirstRender) {
    container.addEventListener('click', (event) => {
      if (event.target.matches('[data-compare]')) {
        compare();
      }
      if (event.target.matches('[data-clear]')) {
        clearItems();
      }
    });
  }

  container.hidden = items.length === 0;
  container.innerHTML = `
    <span>${items.map((item) => item.name).join(', ')}</span>
    <button type="button" data-clear>Clear all</button>
    <button type="button" data-compare ${canCompare ? '' : 'disabled'}>
      Compare ${items.length}
    </button>
  `;
});

search.addWidgets([
  searchBox({ container: '#searchbox' }),
  hits({
    container: '#hits',
    templates: {
      item(hit, { html }) {
        // Every selection change re-renders the hits, so the
        // checkboxes stay in sync with the bar.
        const { isSelected, toggleItem, canAddItems } =
          search.renderState.instant_search?.compare ?? {};
        const selected = isSelected?.(hit.objectID) ?? false;

        return html`
          <article>
            <h2>${hit.name}</h2>
            <label>
              <input
                type="checkbox"
                checked=${selected}
                disabled=${!toggleItem || (!selected && !canAddItems)}
                onChange=${() => toggleItem?.(hit)}
              />
              Compare
            </label>
          </article>
        `;
      },
    },
  }),
  compareBarCustom({
    minItems: 2,
    maxItems: 3,
    configurationId: 'CONFIGURATION_ID',
  }),
  chat({
    container: '#chat',
    agentId: 'AGENT_ID',
  }),
]);

search.start();
```
