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

# Upgrade InstantSearch.js

> Upgrade your InstantSearch.js version

export const UserToken = () => <Tooltip tip="A user token is a pseudonymous ID that represents an individual user across Algolia searches and events. It links queries, clicks, and conversions to a user profile, enabling user-level analytics, personalization, and recommendations." cta="User token" href=" /doc/guides/sending-events/concepts/usertoken">
    user token
  </Tooltip>;

export const SearchQuery = () => <Tooltip tip="The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.">
    search query
  </Tooltip>;

export const Index = () => <Tooltip tip="An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.">
    index
  </Tooltip>;

## Upgrade templates

Starting from InstantSearch.js v4.46.0, **string-based and Hogan.js templates are deprecated.**

If you're using Hogan.js templates or HTML strings using string-based templates,
you can replace them either with safe HTML strings using the provided `html` tagged template, or JSX templates.

These new templating options are safer against cross-site scripting (XSS) attacks,
perform better, and are more accessible,
because they reuse and patch the existing DOM instead of replacing it entirely at each render.

For more information, see:

* [Using HTML strings](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#using-html-strings)
* [Using JSX](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#using-jsx)

### Interpolation

You can interpolate dynamic values using [template literals placeholders](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#string_interpolation).

All available dynamic values are exposed within the scope of the template function.

<CodeGroup>
  ```js Tagged templates theme={"system"}
  toggleRefinement({
    // ...
    templates: {
      labelText({ count }, { html }) {
        return html`Free shipping (${count})`;
      },
    },
  });
  ```

  ```js Hogan.js theme={"system"}
  // Don't do this, use `html` instead

  toggleRefinement({
    // …
    templates: {
      labelText: 'Free shipping ({{ count }})',
    },
  });
  ```

  ```js Classic HTML strings theme={"system"}
  // Don't do this, use `html` instead

  toggleRefinement({
    // ...
    templates: {
      labelText({ count }) {
        return `Free shipping (${count})`;
      },
    },
  });
  ```
</CodeGroup>

#### Highlighting and snippeting

In both [`hits`](/doc/api-reference/widgets/hits/js) and [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widgets,
templates expose a set of built-in components to handle highlighting and snippeting.

<CodeGroup>
  ```js Tagged templates theme={"system"}
  hits({
    // ...
    templates: {
      item(hit, { html, components }) {
        return html`
          <h2>${components.Highlight({ hit, attribute: 'name' })}</h2>
          <p>${components.Snippet({ hit, attribute: 'description' })}</p>
        `;
      },
    },
  });
  ```

  ```js Hogan.js theme={"system"}
  // Don't do this, use `html` instead

  hits({
    // ...
    templates: {
      item: `
        <h2>{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}</h2>
        <p>{{#helpers.snippet}}{ "attribute": "description" }{{/helpers.snippet}}</p>
      `,
    },
  });
  ```

  ```js Classic HTML strings theme={"system"}
  // Don't do this, use `html` instead

  hits({
    // ...
    templates: {
      item(hit) {
        return `
          <h2>${instantsearch.highlight({ attribute: 'name', hit })}</h2>
          <p>${instantsearch.snippet({ attribute: 'description', hit })}</p>
        `;
      },
    },
  });
  ```
</CodeGroup>

### Loops

You can use plain JavaScript to build dynamic templates.

For example, you can use [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)to loop over an array and display a list.

<CodeGroup>
  ```js Tagged templates theme={"system"}
  ratingMenu({
    // …
    templates: {
      item({ url, stars }, { html }) {
        return html`<a href="${url}">
          ${stars.map((_, index) => html`<svg key="${index}"><!-- … --></svg>`)}
        </a>`;
      },
    },
  });
  ```

  ```js Hogan.js theme={"system"}
  // Don't do this, use `html` instead

  ratingMenu({
    // …
    templates: {
      item: `
        <a href="{{url}}">
          {{#stars}}
            <svg><!-- … --></svg>
          {{/stars}}
        </a>
      `,
    },
  });
  ```

  ```js Classic HTML strings theme={"system"}
  // Don't do this, use `html` instead

  ratingMenu({
    // …
    templates: {
      item({ url, stars }) {
        return `<a href="${url}">
          ${stars.map((_, index) => `<svg><!-- … --></svg>`)}
        </a>`;
      },
    },
  });
  ```
</CodeGroup>

<Tip>
  Passing a unique `key` attribute is helpful when mapping over items.
  It helps the virtual DOM keep track of each element when they change, and update the UI efficiently.
</Tip>

### Conditional rendering

To conditionally render a part of your UI,
you can use a [short-circuit operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND)
or a [ternary](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator).

Since templates are functions, you can also use conditional `return` statements to provide alternative templates.

<CodeGroup>
  ```js Tagged templates theme={"system"}
  stats({
    // …
    templates: {
      text({ nbHits }, { html }) {
        if (nbHits === 0) {
          return html`<p>No results.</p>`;
        }

        return html`<p>${nbHits} result${nbHits > 1 ? 's' : ''} found.</p>`;
      },
    },
  });
  ```

  ```js Hogan.js theme={"system"}
  // Don't do this, use `html` instead

  stats({
    // …
    templates: {
      text: `
        {{#hasNoResults}}<p>No results.</p>{{/hasNoResults}}
        {{^hasNoResults}}
          <p>{{nbHits}} result{{#hasManyResults}}results{{/hasManyResults}} found.</p>
        {{/hasNoResults}}
      `,
    },
  });
  ```

  ```js Classic HTML strings theme={"system"}
  // Don't do this, use `html` instead

  stats({
    // …
    templates: {
      text({ nbHits }) {
        if (nbHits === 0) {
          return `<p>No results.</p>`;
        }

        return `<p>${nbHits} result${nbHits > 1 ? 's' : ''} found.</p>`;
      },
    },
  });
  ```
</CodeGroup>

## Upgrade event tracking

**Starting from v4.55.0, InstantSearch simplifies the event tracking process with the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option.**
You no longer need to install the [`search-insights`](/doc/libraries/search-insights) library or set up the [`insights`](/doc/api-reference/widgets/insights/js) middleware yourself.

Here are some benefits when using the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option:

* It better handles the <UserToken />. Once you set it, all search and event tracking calls include the token.
* It automatically sends default events from built-in widgets such as [`refinementList`](/doc/api-reference/widgets/refinement-list/js), [`menu`](/doc/api-reference/widgets/menu/js), etc. You can also change the event payloads, or remove them altogether.
* It lets you send custom events from your custom widgets.
* It simplifies forwarding events to third-party trackers.

If you've been tracking events directly with [`search-insights`](/doc/libraries/search-insights) or with the [`insights`](/doc/api-reference/widgets/insights/js) middleware, you should:

1. Upgrade InstantSearch.js to v4.55.0 or greater
2. [Migrate](#use-the-insights-option) from using the [`insights`](/doc/api-reference/widgets/insights/js) middleware to the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option
3. Either [update](#update-search-insights) or [remove](#remove-search-insights) the [`search-insights`](/doc/libraries/search-insights) library

### Use the Insights option

**Starting from v4.55.0, InstantSearch lets you enable event tracking with the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option.** You no longer need to set up the [`insights`](/doc/api-reference/widgets/insights/js) middleware yourself.

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  // …
  insights: true,
});
```

If you had already set up the [`insights`](/doc/api-reference/widgets/insights/js) middleware in your code, you can now remove it and move its configuration to the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option.

```diff JavaScript icon=code theme={"system"}
- import { createInsightsMiddleware } from 'instantsearch.js/es/middlewares';

const search = instantsearch({
  insights: {
+    insightsInitParams: {
+      useCookie: false,
+    },
  },
});

- search.use(
-   createInsightsMiddleware({
-     insightsInitParams: {
-       useCookie: false,
-     },
-   })
- );
```

### Update search-insights

**Starting from v4.55.0, InstantSearch can load [`search-insights`](/doc/libraries/search-insights) for you so the [`insightsClient`](/doc/api-reference/widgets/insights/js#param-insights-client) option is no longer required.**

If you prefer loading it yourself, make sure to update [`search-insights`](/doc/libraries/search-insights) to at least v2.4.0 and forward the reference to [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights).

If you're using the UMD bundle with a `<script>` tag, make sure to update the full code snippet (not just the version):

<Insights />

If you're using a package manager, you can upgrade it to the latest version.

```sh Command line icon=square-terminal theme={"system"}
npm install search-insights
```

Now you can pass the reference to the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option.

<CodeGroup>
  ```js UMD theme={"system"}
  const search = instantsearch({
    insights: {
      insightsClient: window.aa,
    },
  });
  ```

  ```js Module theme={"system"}
  import aa from 'search-insights';

  const search = instantsearch({
    insights: {
      insightsClient: aa,
    },
  });
  ```
</CodeGroup>

Otherwise, you can [remove it and let InstantSearch handle it for you](#remove-search-insights).

### Remove search-insights

**Starting from v4.55.0, InstantSearch loads [`search-insights`](/doc/libraries/search-insights) for you from jsDelivr if not detected in the page.** If you've installed [`search-insights`](/doc/libraries/search-insights), you can now remove it.

If you're using the UMD bundle with a `<script>` tag, you can remove the snippet in any page that uses InstantSearch:

```diff HTML icon=code-xml theme={"system"}
- <script>
-   var ALGOLIA_INSIGHTS_SRC = "https://cdn.jsdelivr.net/npm/search-insights@2.17.3/dist/search-insights.min.js";
-
-   !function(e,a,t,n,s,v,i,c){e.AlgoliaAnalyticsObject=s,e[s]=e[s]||function(){
-   (e[s].queue=e[s].queue||[]).push(arguments)},e[s].version=(n.match(/@([^\/]+)\/?/) || [])[1],i=a.createElement(t),c=a.- getElementsByTagName(t)[0],
-   i.async=1,i.src=n,c.parentNode.insertBefore(i,c)
-   }(window,document,"script",ALGOLIA_INSIGHTS_SRC,"aa");
- </script>
```

If you're using a package manager, you can remove it from your dependencies.

```sh Command line icon=square-terminal theme={"system"}
npm uninstall search-insights
```

Then you can remove the reference to [`search-insights`](/doc/libraries/search-insights) from your code:

<CodeGroup>
  ```diff UMD theme={"system"}
  const search = instantsearch({
      insights: {
  -     insightsClient: window.aa,
      },
    });
  ```

  ```diff Module theme={"system"}
  - const aa = require('search-insights');
  // or
  - import aa from 'search-insights';

    const search = instantsearch({
      insights: {
  -     insightsClient: aa,
      },
    });
  ```
</CodeGroup>

<Info>
  InstantSearch loads [`search-insights`](/doc/libraries/search-insights) from the jsDelivr CDN, which requires that your site or app allows script execution from foreign resources. Check the [security best practices](/doc/guides/security/security-best-practices) for recommendations.
</Info>

### No longer needed configuration

#### `clickAnalytics: true`

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.configure({
  clickAnalytics: true,
}),
```

This isn't needed anymore because it's automatically added by the middleware.

#### `getInsightsAnonymousUserToken`

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.configure({
  userToken: instantsearch.getInsightsAnonymousUserToken()
});
```

You no longer need this.
Once you integrate the `insights` middleware, it initially sets the anonymous `userToken`.
As soon as you set another `userToken`, it automatically syncs it between search and analytics calls.

#### `insightsClient` at InstantSearch

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  searchClient,
  indexName: 'INDEX_NAME',
  insightsClient: window.aa,
});
```

Now that you are using the `Insights` middleware, you don't need to pass `aa` to `instantsearch()` anymore.

### `hits` and `infiniteHits` template

You might have templates in your [`hits`](/doc/api-reference/widgets/hits/js) or [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widgets that look like the following:

```html HTML icon=code-xml theme={"system"}
<button
  ${instantsearch.insights('convertedObjectIDsAfterSearch', {
    eventName: 'Product Added',
    objectIDs: [hit.objectID]
  })}
>
    Add to cart
</button>
```

These are now deprecated. Instead, you can use the `sendEvent` function.

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.hits({
  container: '#hits',
  templates: {
    item(hit, { html, sendEvent, components }) {
      return html`
          <article>
            <h3>${components.Highlight({ attribute: 'name', hit })}</h3>

            <button
              onClick=${() => sendEvent('conversion', hit, 'Product Added')}
            >
                Add to cart
                <!-- clicking this button sends a `conversion` event -->
            </button>
          </article>
        `;
    }
  }
})
```

### `analytics` widget

The [`analytics`](/doc/api-reference/widgets/analytics/js) widget has been deprecated in favor of the [`insights`](/doc/api-reference/widgets/insights/js) middleware.

When the built-in widgets trigger [default events](/doc/guides/building-search-ui/events/js#default-events), you can intercept them using the [`insights`](/doc/api-reference/widgets/insights/js) middleware's [`onEvent`](/doc/api-reference/widgets/insights/js#param-on-event) hook and send them to third-party trackers.

```js JavaScript icon=code theme={"system"}
import { createInsightsMiddleware } from 'instantsearch.js/es/middlewares';
// Or, use `instantsearch.middlewares.createInsightsMiddleware()`

const indexName = '<your-index-name>';
const search = instantsearch({
  indexName,
  // ...
});

const insightsMiddleware = createInsightsMiddleware({
  insightsClient: window.aa,
  onEvent: (event, aa) => {
    const { insightsMethod, payload, widgetType, eventType } = event;

    // Send the event to Algolia
    aa(insightsMethod, payload);

    // Send click events on Hits to a third-party tracker
    if (widgetType === 'ais.hits' && eventType === 'click') {
      thirdPartyTracker.send('Product Clicked', payload);
    }
  }
});

search.use(insightsMiddleware);
```

If you want to send events every time the <SearchQuery /> or refinements change,
you can add a custom middleware to listen to [`uiState`](/doc/api-reference/widgets/ui-state/js) changes with the `onStateChange` function.

```js JavaScript icon=code theme={"system"}
const indexName = '<your-index-name>';
const search = instantsearch({
  indexName,
  // ...
});

const analyticsMiddleware = () => {
  return {
    onStateChange({ uiState }) {
      // Google Analytics (Universal Analytics)
      window.ga('set', 'page', window.location.pathname + window.location.search);
      window.ga('send', 'pageView');

      // Google Analytics V4
      gtag('event', 'page_view', {
        page_location: window.location.pathname + window.location.search,
      });

      // Google Tag Manager (GTM)
      // You can use `uiState` to make payloads for third-party trackers.
      dataLayer.push({
        'event': 'search',
        'Search Query': uiState[indexName].query,
        'Brand': uiState[indexName].refinementList['brand'].join(','),
      });
    },
    subscribe() {},
    unsubscribe() {},
  }
}

search.use(analyticsMiddleware);
```

You can also use external libraries like `lodash.debounce` to debounce the events you send.

```js JavaScript icon=code theme={"system"}
import debounce from 'lodash.debounce';

const indexName = '<your-index-name>';
const search = instantsearch({
  indexName,
  // ...
});

const sendEventDebounced = debounce((uiState) => {
  // Google Analytics
  window.ga('set', 'page', window.location.pathname + window.location.search);
  window.ga('send', 'pageView');

  // Google Analytics V4
  gtag('event', 'page_view', {
    page_location: window.location.pathname + window.location.search,
  });

  // Google Tag Manager (GTM)
  dataLayer.push({
    'event': 'search',
    'Search Query': uiState[indexName].query,
    'Brand': uiState[indexName].refinementList['brand'].join(','),
  });
}, 3000);

const analyticsMiddleware = () => {
  return {
    onStateChange({ uiState }) {
      sendEventDebounced(uiState);
    },
    subscribe() {},
    unsubscribe() {},
  }
}

search.use(analyticsMiddleware);
```

## Upgrade to Recommend

The [`relatedProducts`](/doc/api-reference/widgets/related-products/js) widget is now available instead of the previous experimental widget `EXPERIMENTAL_relatedItems`. To migrate, you change the widget name and update the props:

```diff JavaScript icon=code theme={"system"}
- import { EXPERIMENTAL_relatedItems } from 'instantsearch.js/es/widgets';
+ import { relatedProducts } from 'instantsearch.js/es/widgets';

search.addWidgets([
-  EXPERIMENTAL_relatedItems({
-    hit: { objectID: '123' },
-  }),
-  hits({ container: '#related-products' }),
+  relatedProducts({
+    container: '#related-products',
+    objectIDs: ['123'],
+  }),
]);
```

## Upgrade from v3 to v4

The latest version of InstantSearch focuses on federated search and bundle size. This required some changes that might impact your app.

### Federated search (multi-index)

If you were already using federated search (for example by synchronizing two [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) instances with the [`searchFunction`](/doc/api-reference/widgets/instantsearch/js#param-search-function)),
the implementation is now simpler. Instead, try the new [`index`](/doc/api-reference/widgets/index-widget/js) widget,
to which you can attach more widgets.
For example:

```js JavaScript icon=code theme={"system"}
const search = instantsearch({ indexName: 'primary', /* ... */ });

search.addWidgets([
  searchBox(),
  hits(),
  index({ indexName: 'secondary' }).addWidgets([
    searchBox(),
    hits(),
  ])
]);
```

To learn more, see [Multi-index search with InstantSearch.js](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/js)

### Routing

Even if you aren't using multi-index search, the way in which [`uiState`](/doc/api-reference/widgets/ui-state/js) is stored has changed.
It used to look like this:

```json JSON icon=braces theme={"system"}
{
  "query": "value",
  "page": 5
}
```

It now looks like this:

```json JSON icon=braces theme={"system"}
{
  "indexName": {
    "query": "value",
    "page": 5
  }
}
```

If you are using the default state mapping ([`simpleStateMapping`](/doc/api-reference/widgets/simple-state-mapping/js)) with the current version, you can replace it with [`singleIndexStateMapping('yourIndexName')`](/doc/api-reference/widgets/single-index-state-mapping/js). You have to change the code as followed:

```diff JavaScript icon=code theme={"system"}
 instantsearch({
   indexName: 'myIndex',
   routing: {
-    stateMapping: instantsearch.stateMappings.simple(),
+    stateMapping: instantsearch.stateMappings.singleIndex('myIndex'),
   }
   // ...
 });
```

If you are using a custom state mapping, you have to loop over the outer level of the `index` widget and add this extra level to the `routeToState`. You can check the [source](https://github.com/algolia/instantsearch/blob/master/packages/instantsearch.js/src/lib/stateMappings/singleIndex.ts) for a reference on how to implement this.

For example, a `stateMapping` that maps a subset of properties would change like this:

```js JavaScript icon=code theme={"system"}
// Before
const stateMapping = {
  stateToRoute(uiState) {
    return {
      query: uiState.query,
      page: uiState.page,
      // ...
    };
  },

  routeToState(routeState) {
    return {
      query: routeState.query,
      page: routeState.page,
      // ...
    };
  },
};

// After
const stateMapping = {
  stateToRoute(uiState) {
    const indexUiState = uiState[indexName];
    return {
      query: indexUiState.query,
      page: indexUiState.page,
      // ...
    };
  },

  routeToState(routeState) {
    return {
      [indexName]: {
        query: routeState.query,
        page: routeState.page,
        // ...
      },
    };
  },
};
```

#### Configure

The [`configure`](/doc/api-reference/widgets/configure/js) widget is now included in [`uiState`](/doc/api-reference/widgets/ui-state/js).
If you want to exclude it from the URL you can use the default `stateMapping`s or exclude it in your custom state mapping.
A good reason to exclude the configure widget from the UI state is to prevent users from adding any search parameters.

You must exclude this widget in both the `stateToRoute`, to keep it from appearing in the URL and `routeToState`, so that the URL doesn't apply to the state.

Check the [`stateMapping` source code](https://github.com/algolia/instantsearch/blob/master/packages/instantsearch.js/src/lib/stateMappings/simple.ts) for implementation details.

### Algolia search helper

This release includes version 3 of the [`algoliasearch-helper` package](https://www.npmjs.com/package/algoliasearch-helper/). **If you're using the built-in widgets or connectors, nothing changes for you.**

This version of `algoliasearch-helper` no longer includes [Lodash](https://lodash.com/), which reduces its bundle size (from 27.5 KB to 9.1 KB Gzipped).
If you're using any methods from the `helper`, `searchParameters` or `searchResults`, refer to the package's [change log](https://instantsearchjs.netlify.app/algoliasearch-helper-js/upgrade.html).

### Search parameters

The option `searchParameters` was removed from the [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) widget.
You can replace it with the [`configure`](/doc/api-reference/widgets/configure/js) widget:

```diff JavaScript icon=code theme={"system"}
const search = instantsearch({
   // ...
-  searchParameters: {
-    hitsPerPage: 5,
-  },
});

+ search.addWidgets([
+   instantsearch.widgets.configure({
+     hitsPerPage: 5,
+   }),
+ ]);
```

You can now add [`initialUiState`](/doc/api-reference/widgets/instantsearch/js#param-initial-ui-state) to your `InstantSearch` widget. This overwrites specific `searchParameters` that are otherwise set during widget instantiation.  `initialUiState` is only taken into account if a widget owning that "state" is mounted. A warning appears in development mode explaining which widget needs to be added for the UI state to have an effect.

An example is a [`refinementList`](/doc/api-reference/widgets/refinement-list/js) widget:

```diff JavaScript icon=code theme={"system"}
const search = instantsearch({
   // ...
-  searchParameters: {
-    disjunctiveFacets: ['brand'],
-    disjunctiveFacetsRefinements: {
-      brand: ['Apple'],
-    },
-  },
+  initialUiState: {
+    refinementList: {
+      brand: ['Apple'],
+    },
+  },
});

search.addWidgets([
  refinementList({ attribute: 'brand' }),
]);

// or when there should be no display for this widget:

const virtualRefinementList = connectRefinementList(() => null);

search.addWidgets([
  virtualRefinementList({ attribute: 'brand' }),
]);
```

### `addWidget` and `removeWidget`

The `search.addWidget` function is deprecated in favor of `search.addWidgets`, which accepts an array of widgets.

Using arrays makes it clearer to see the structure of nested widgets.
`search.removeWidget` is deprecated in favor of `search.removeWidgets`, which also accepts an array of widgets.

### Custom widgets

Because version 3 of the `algoliasearch-helper` package is used,
you have to replace the `getConfiguration` lifecycle with `getWidgetSearchParameters` and `getWidgetState`.

This also means that your custom widget takes part in the routing.
You can exclude it from the URL with `stateMapping`.

```js JavaScript icon=code theme={"system"}
const widget = {
  getConfiguration(searchParams) {
    return {
      disjunctiveFacets: ['myAttribute'],
    };
  },
  getWidgetSearchParameters(searchParameters, { uiState }) {
    return searchParameters
      .addDisjunctiveFacetRefinement(
        'myAttribute',
        uiState.myWidgetName.myAttribute
      );
  },
  getWidgetState(uiState, { searchParameters }) {
    return {
      ...uiState,
      myWidgetName: {
        myAttribute: searchParameters.getDisjunctiveRefinements('myAttribute')
      }
    };
  }
};
```

Becomes:

```js JavaScript icon=code theme={"system"}
const widget = {
  getWidgetSearchParameters(searchParameters, { uiState }) {
    return searchParameters
      .addDisjunctiveFacet('myAttribute')
      .addDisjunctiveFacetRefinement(
        'myAttribute',
        uiState.myWidgetName.myAttribute
      );
  },
  getWidgetState(uiState, { searchParameters }) {
    return {
      ...uiState,
      myWidgetName: {
        myAttribute: searchParameters.getDisjunctiveRefinements('myAttribute')
      }
    };
  }
};
```

### `connectAutocomplete`

The `indices` option was removed in favor of `index` widgets (see the [federated search section](#federated-search-multi-index)).
For example, the following code:

```js JavaScript icon=code theme={"system"}
const autocomplete = connectAutocomplete(() => {/* ... */});

search.addWidget(
  autocomplete({
    indices: [{
      name: 'additional'
    }]
  })
);
```

Should be replaced with this:

```js JavaScript icon=code theme={"system"}
const autocomplete = connectAutocomplete(() => {/* ... */});

search.addWidgets([
  index({ indexName: 'additional' }),
  autocomplete()
]);
```

### `onHistoryChange`

The `onHistoryChange` function is removed.
To subscribe to changes in the URL, create a custom `router` and attach to the `write` hook. For example:

```js JavaScript icon=code theme={"system"}
const router = historyRouter()
const originalWrite = router.write.bind(router)
router.write = state => {
  console.log('listen to route state here');
  originalWrite(state)
}
```

### `router`

The `dispose` function on the `router` interface is now required and its signature is updated (see the example below).
This change only impacts you if you use a custom router.

```ts TypeScript icon=code theme={"system"}
interface Router {
  // ...
  // Before
  dispose?({ helper, state }: { helper: AlgoliaSearchHelper, state: SearchParameters }): SearchParameters | void
  // After
  dispose(): void
}
```

### `pagination`

The `noRefinementRoot` CSS class gets added once there are no more possible refinements.
It no longer gets applied on the first page, which was the wrong behavior.

## Upgrade from v2 to v3

InstantSearch v3 introduces some breaking changes in the widgets' naming, options, and markup.

### What's new

#### Search from your own search client

Since InstantSearch.js 2.8.0,
it's possible to [search from your own backend](/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-instantsearch/js) using the [`searchClient`](/doc/api-reference/widgets/instantsearch/js#param-search-client) option.

This option is now the way to plug InstantSearch to either Algolia's [official JavaScript client](https://github.com/algolia/algoliasearch-client-javascript) or to yours. This means that the official search client isn't bundled in InstantSearch, allowing for more flexibility. This results in a smaller bundle size when you're not searching directly from the frontend, or that you use multiple instances of the search client.

#### Predictable DOM structure

InstantSearch.js v3 becomes more predictable by generating a DOM that follows Algolia's InstantSearch.css specification.
All flavors are now aligned, which makes it easier to migrate from one library flavor to another.
The options and the DOM output are similar whether you use React InstantSearch, Vue InstantSearch, or InstantSearch.js.

#### Optimize InstantSearch based on your environment

The new bundle comes in two forms:

* **Development bundle.** This is a heavier bundle that helps developers better debug in the development phase of an InstantSearch app. It's more verbose and gives clues and warnings about the library usage.
* **Production bundle.** This is a lighter bundle that doesn't include any development warnings. Use this when deploying your app in a production environment.

### Imports

#### UMD (Universal Module Definition)

New naming has been introduced for the UMD imports. This makes it clearer which InstantSearch.js bundle to use either in development or in production. The production bundle will get lighter over time as it won't include the runtime warnings and documentation.

##### Before

```txt theme={"system"}
dist
├── instantsearch.js
├── instantsearch.js.map
├── instantsearch.min.js
└── instantsearch.min.js.map
```

##### After

```txt theme={"system"}
dist
├── instantsearch.development.js
├── instantsearch.development.js.map
├── instantsearch.production.min.js
└── instantsearch.production.min.js.map
```

#### CommonJS (CJS)

##### Before

```js JavaScript icon=code theme={"system"}
const instantsearch = require('instantsearch.js');
```

##### After

```js JavaScript icon=code theme={"system"}
const instantsearch = require('instantsearch.js').default;
```

#### ES (ECMAScript)

No changes.

### InstantSearch

The variables `appId` and `apiKey` are replaced by `searchClient`.

#### Earlier usage

1. [Import `InstantSearch.js`](/doc/guides/building-search-ui/installation/js)
2. Initialize InstantSearch

   ```js JavaScript icon=code theme={"system"}
   const search = instantsearch({
     indexName: 'indexName',
     appId: 'appId',
      apiKey: 'apiKey',
   });

   search.start();
   ```

##### New usage

1. [Import `algoliasearch`](/doc/libraries/sdk/install) (prefer the [lite version](https://github.com/algolia/algoliasearch-client-javascript#search-onlylite-client) for search only)
2. [Import `InstantSearch.js`](/doc/guides/building-search-ui/installation/js)
3. Initialize InstantSearch with the `searchClient` option

   ```js JavaScript icon=code theme={"system"}
   const search = instantsearch({
   indexName: 'indexName',
   searchClient: algoliasearch('appId', 'apiKey'),
   });

   search.start();
   ```

#### `transformData` is replaced by `transformItems`

Since InstantSearch.js first public release, you can customize the values used in the widgets.
This method was letting you map 1-1 the values with other values. With React InstantSearch,
a slightly different API was implemented that lets you map over the list of values and to change their content.

##### Earlier usage

```js JavaScript icon=code theme={"system"}
search.addWidgets([
  instantsearch.widget.refinementList({
    container: '#facet',
    attributeName: 'facet',
    transformData: {
      item: data => {
        data.count = 0;
        return data;
      },
    },
  })]
);
```

##### New usage

```js JavaScript icon=code theme={"system"}
search.addWidgets([
  instantsearch.widget.refinementList({
    container: '#facet',
    attribute: 'facet',
    transformItems: items =>
      items.map(item => ({
        ...item,
        count: 0,
      })),
  })
]);
```

#### Highlights and snippets

Highlighting is a powerful tool for showing users why results match their queries.
Earlier versions of InstantSearch used internal mechanisms or Hogan.js to implement highlighting within widget templates.
Algolia now provides `html` tagged templates.

For more information, see:

* [Highlights and snippets](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlight-and-snippet-your-search-results)
* [`html` tagged templates](#upgrade-templates)

##### Earlier usage

<CodeGroup>
  ```js string theme={"system"}
  search.addWidgets([
    instantsearch.widget.hits({
      container: '#hits',
      templates: {
        item: '{{{ _highlightResult.name.value }}}',
      },
    })
  ]);
  ```

  ```js Hogan.js theme={"system"}
  search.addWidgets([
    instantsearch.widget.hits({
      container: '#hits',
      templates: {
        item: '{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}',
      },
    })
  ]);
  ```
</CodeGroup>

##### New usage

```js JavaScript icon=code theme={"system"}
search.addWidgets([
  instantsearch.widget.hits({
    container: '#hits',
    templates: {
      item: (hit, { components }) => components.Highlight({ attribute: 'name', hit }),
    },
  })
]);
```

#### `urlSync` is dropped

If you were using the `urlSync` option,
you should now migrate to the new [`routing`](/doc/api-reference/widgets/instantsearch/js#param-routing) feature.

Here are the elements you need to migrate:

* `urlSync: true` becomes `routing: true`
* `threshold` becomes `routing: { router: instantsearch.routers.history({ writeDelay: 400 }) }`
* `mapping` and `trackedParameters` are replaced with `stateMapping`. Read "[User friendly URLs](/doc/guides/building-search-ui/going-further/routing-urls/js#seo-friendly-urls)" to know how to configure it
* `useHash` is removed but can be achieved using an advanced configuration of the [history router](/doc/guides/building-search-ui/going-further/routing-urls/js)
* `getHistoryState` is removed but can be achieved using an advanced configuration of the [history router](/doc/guides/building-search-ui/going-further/routing-urls/js)

#### `collapsible` is dropped

`collapsible` is replaced by the [`collapsed`](/doc/api-reference/widgets/panel/js#param-collapsed) option in the [`panel`](/doc/api-reference/widgets/panel/js) widget.

#### `autoHideContainer` is dropped

`autoHideContainer` is replaced by the [`hidden`](/doc/api-reference/widgets/panel/js#param-hidden) option in the [`panel`](/doc/api-reference/widgets/panel/js) widget.

#### `createAlgoliaClient` is dropped

`createAlgoliaClient` is replaced by `searchClient`.

#### `createQueryString` is dropped

URL synchronization is done with Routing alone.

### Widgets

#### Breadcrumb

##### CSS classes

| Before                          | After                           |
| ------------------------------- | ------------------------------- |
| `ais-breadcrumb`                | `ais-Breadcrumb`                |
|                                 | `ais-Breadcrumb--noRefinement`  |
| `ais-breadcrumb`                | `ais-Breadcrumb-list`           |
| `ais-breadcrumb--separator`     | `ais-Breadcrumb-separator`      |
| `ais-breadcrumb--label`         | `ais-Breadcrumb-link`           |
| `ais-breadcrumb--disabledLabel` |                                 |
|                                 | `ais-Breadcrumb-item`           |
|                                 | `ais-Breadcrumb-item--selected` |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-Breadcrumb">
  <ul class="ais-Breadcrumb-list">
    <li class="ais-Breadcrumb-item">
      <a class="ais-Breadcrumb-link" href="#">Home</a>
    </li>
    <li class="ais-Breadcrumb-item">
      <span class="ais-Breadcrumb-separator" aria-hidden="true">></span>
      <a class="ais-Breadcrumb-link" href="#">Cooking</a>
    </li>
    <li class="ais-Breadcrumb-item ais-Breadcrumb-item--selected">
      <span class="ais-Breadcrumb-separator" aria-hidden="true">></span>
      Kitchen textiles
    </li>
  </ul>
</div>
```

#### ClearRefinements (formerly ClearAll)

##### Options

| Before              | After                |
| ------------------- | -------------------- |
| `excludeAttributes` | `excludedAttributes` |

##### CSS classes

| Before                | After                                   |
| --------------------- | --------------------------------------- |
| `ais-clear-all`       | `ais-ClearRefinements`                  |
| `ais-clear-all--body` |                                         |
| `ais-clear-all--link` |                                         |
|                       | `ais-ClearRefinements-button`           |
|                       | `ais-ClearRefinements-button--disabled` |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-ClearRefinements">
  <button class="ais-ClearRefinements-button">
    Clear refinements
  </button>
</div>
```

#### CurrentRefinements (formerly CurrentRefinedValues)

##### Options

| Before                 | After                             |
| ---------------------- | --------------------------------- |
| `attributes`           | `includedAttributes`              |
|                        | `excludedAttributes`              |
| `onlyListedAttributes` | use `includedAttributes`          |
| `clearsQuery`          | use `excludedAttributes`          |
| `clearAll`             | use the `clearRefinements` widget |

* `clearsQuery` can be replaced by `excludedAtributes: []`.

##### CSS classes

| Before                                  | After                                  |
| --------------------------------------- | -------------------------------------- |
| `ais-current-refined-values`            | `ais-CurrentRefinements`               |
| `ais-current-refined-values--list`      | `ais-CurrentRefinements-list`          |
| `ais-current-refined-values--item`      | `ais-CurrentRefinements-item`          |
| `ais-current-refined-values--link`      | `ais-CurrentRefinements-label`         |
| `ais-current-refined-values--count`     |                                        |
|                                         | `ais-CurrentRefinements-category`      |
|                                         | `ais-CurrentRefinements-categoryLabel` |
|                                         | `ais-CurrentRefinements-delete`        |
|                                         | `ais-CurrentRefinements-query`         |
| `ais-current-refined-values--clear-all` |                                        |

##### Markup

###### Default

```html HTML icon=code-xml theme={"system"}
<div class="ais-CurrentRefinements">
  <ul class="ais-CurrentRefinements-list">
    <li class="ais-CurrentRefinements-item">
      <span class="ais-CurrentRefinements-label">
        Category:
      </span>
      <span class="ais-CurrentRefinements-category">
        <span class="ais-CurrentRefinements-categoryLabel">Movies & TV Shows</span>
        <button class="ais-CurrentRefinements-delete">✕</button>
      </span>
      <span class="ais-CurrentRefinements-category">
        <span class="ais-CurrentRefinements-categoryLabel">Others</span>
        <button class="ais-CurrentRefinements-delete">✕</button>
      </span>
    </li>
  </ul>
</div>
```

###### With `includesQuery` and a query

```html HTML icon=code-xml theme={"system"}
<div class="ais-CurrentRefinements">
  <ul class="ais-CurrentRefinements-list">
    <li class="ais-CurrentRefinements-item">
      <span class="ais-CurrentRefinements-label">
        Category:
      </span>
      <span class="ais-CurrentRefinements-category">
        <span class="ais-CurrentRefinements-categoryLabel">Movies & TV Shows</span>
        <button class="ais-CurrentRefinements-delete">✕</button>
      </span>
      <span class="ais-CurrentRefinements-category">
        <span class="ais-CurrentRefinements-categoryLabel">Others</span>
        <button class="ais-CurrentRefinements-delete">✕</button>
      </span>
    </li>
    <li class="ais-CurrentRefinements-item">
      <span class="ais-CurrentRefinements-label">
        Query:
      </span>
      <span class="ais-CurrentRefinements-category">
        <span class="ais-CurrentRefinements-categoryLabel">
          <q>My query</q>
        </span>
        <button class="ais-CurrentRefinements-delete">✕</button>
      </span>
    </li>
  </ul>
</div>
```

#### Geographical search

##### Options

| Before                      | After                                                  |
| --------------------------- | ------------------------------------------------------ |
| `customHTMLMarker.template` | `templates.HTMLMarker`                                 |
| `paddingBoundingBox`        | Removed                                                |
| `enableGeolocationWithIP`   | Removed - use the Configure widget instead (see below) |
| `position`                  | Removed - use the Configure widget instead (see below) |
| `radius`                    | Removed - use the Configure widget instead (see below) |
| `precision`                 | Removed - use the Configure widget instead (see below) |

Since `paddingBoundingBox` conflicted with the `routing` option it was removed to support URLSync for the GeoSearch widget.

##### `enableGeolocationWithIP`

**Before:**

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.geoSearch({
  googleReference: window.google,
  enableGeolocationWithIP: true,
  container,
});
```

**After:**

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.configure({
  aroundLatLngViaIP: true,
});

instantsearch.widgets.geoSearch({
  googleReference: window.google,
  container,
});
```

##### `position`

**Before:**

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.geoSearch({
  googleReference: window.google,
  position: { lat: 40.71, lng: -74.01 },
  container,
});
```

**After:**

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.configure({
  aroundLatLng: '40.71, -74.01',
});

instantsearch.widgets.geoSearch({
  googleReference: window.google,
  container,
});
```

##### `radius`

**Before:**

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.geoSearch({
  googleReference: window.google,
  radius: 1000,
  container,
});
```

**After:**

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.configure({
  aroundRadius: 1000,
});

instantsearch.widgets.geoSearch({
  googleReference: window.google,
  container,
});
```

##### `precision`

**Before:**

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.geoSearch({
  googleReference: window.google,
  precision: 1000,
  container,
});
```

**After:**

```js JavaScript icon=code theme={"system"}
instantsearch.widgets.configure({
  aroundPrecision: 1000,
});

instantsearch.widgets.geoSearch({
  googleReference: window.google,
  container,
});
```

##### CSS classes

| Before                                | After                           |
| ------------------------------------- | ------------------------------- |
| `ais-geo-search`                      | `ais-GeoSearch`                 |
| `ais-geo-search--map`                 | `ais-GeoSearch-map`             |
| `ais-geo-search--controls`            | `ais-GeoSearch-tree`            |
| `ais-geo-search--control`             | `ais-GeoSearch-control`         |
| `ais-geo-search--toggle-label`        | `ais-GeoSearch-label`           |
| `ais-geo-search--toggle-label-active` | `ais-GeoSearch-label--selected` |
| `ais-geo-search--toggle-input`        | `ais-GeoSearch-input`           |
| `ais-geo-search--redo`                | `ais-GeoSearch-redo`            |
|                                       | `ais-GeoSearch-redo--disabled`  |
| `ais-geo-search--clear`               | `ais-GeoSearch-reset`           |

##### Markup

With the control element:

```html HTML icon=code-xml theme={"system"}
<div class="ais-GeoSearch">
  <div class="ais-GeoSearch-map">
    <!-- Map element here -->
  </div>
  <div class="ais-GeoSearch-tree">
    <div class="ais-GeoSearch-control">
      <label class="ais-GeoSearch-label">
        <input class="ais-GeoSearch-input" type="checkbox">
        Search as I move the map
      </label>
    </div>
    <button class="ais-GeoSearch-reset">
      Clear the map refinement
    </button>
  </div>
</div>
```

With the redo button:

```html HTML icon=code-xml theme={"system"}
<div class="ais-GeoSearch">
  <div class="ais-GeoSearch-map">
    <!-- Map element here -->
  </div>
  <div class="ais-GeoSearch-tree">
    <div class="ais-GeoSearch-control">
      <button class="ais-GeoSearch-redo">
        Redo search here
      </button>
    </div>
    <button class="ais-GeoSearch-reset">
      Clear the map refinement
    </button>
  </div>
</div>
```

#### `Hits`

##### Options

| Before          | After           |
| --------------- | --------------- |
| `escapeHits`    | `escapeHTML`    |
| `showMoreLabel` | `loadMoreLabel` |

* `escapeHTML` becomes `true` by default.
* `allItems` template has been removed in favor of `connectHits`

##### CSS classes

| Before            | After             |
| ----------------- | ----------------- |
| `ais-hits`        | `ais-Hits`        |
| `ais-hits--empty` | `ais-Hits--empty` |
|                   | `ais-Hits--list`  |
| `ais-hits--item`  | `ais-Hits--item`  |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-Hits">
  <ol class="ais-Hits-list">
    <li class="ais-Hits-item">
      Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    </li>
    <li class="ais-Hits-item">
      Hit 4397400: Google - Chromecast - Black
    </li>
    <li class="ais-Hits-item">
      Hit 4397400: Google - Chromecast - Black
    </li>
    <li class="ais-Hits-item">
      Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    </li>
    <li class="ais-Hits-item">
      Hit 4397400: Google - Chromecast - Black
    </li>
    <li class="ais-Hits-item">
      Hit 4397400: Google - Chromecast - Black
    </li>
    <li class="ais-Hits-item">
      Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    </li>
    <li class="ais-Hits-item">
      Hit 4397400: Google - Chromecast - Black
    </li>
  </ol>
</div>
```

#### `HitPerPage` (formerly `HitsPerPageSelector`)

##### Options

| Before          | After       |
| --------------- | ----------- |
| `attributeName` | `attribute` |

##### CSS classes

| Before                       | After                    |
| ---------------------------- | ------------------------ |
|                              | `ais-HitsPerPage`        |
| `ais-hits-per-page-selector` | `ais-HitsPerPage-select` |
| `ais-hits-per-page--item`    | `ais-HitsPerPage-option` |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-HitsPerPage">
  <select class="ais-HitsPerPage-select">
    <option class="ais-HitsPerPage-option" value="3">3 per page</option>
    <option class="ais-HitsPerPage-option" value="6">6 per page</option>
  </select>
</div>
```

#### Infinite hits

##### Options

| Before          | After                    |
| --------------- | ------------------------ |
| `escapeHits`    | `escapeHTML`             |
| `loadMoreLabel` | `templates.showMoreText` |

* `escapeHTML` defaults to `true`

##### CSS classes

| Before                              | After                                 |
| ----------------------------------- | ------------------------------------- |
| `ais-infinite-hits`                 | `ais-InfiniteHits`                    |
| `ais-infinite-hits--empty`          | `ais-InfiniteHits--empty`             |
|                                     | `ais-InfiniteHits--list`              |
| `ais-infinite-hits--item`           | `ais-InfiniteHits--item`              |
| `ais-infinite-hits--showmore`       |                                       |
| `ais-infinite-hits--showmoreButton` | `ais-InfiniteHits-loadMore`           |
|                                     | `ais-InfiniteHits-loadMore--disabled` |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-InfiniteHits">
  <ol class="ais-InfiniteHits-list">
    <li class="ais-InfiniteHits-item">
      Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    </li>
    <li class="ais-InfiniteHits-item">
      Hit 4397400: Google - Chromecast - Black
    </li>
    <li class="ais-InfiniteHits-item">
      Hit 4397400: Google - Chromecast - Black
    </li>
    <li class="ais-InfiniteHits-item">
      Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    </li>
    <li class="ais-InfiniteHits-item">
      Hit 4397400: Google - Chromecast - Black
    </li>
    <li class="ais-InfiniteHits-item">
      Hit 4397400: Google - Chromecast - Black
    </li>
    <li class="ais-InfiniteHits-item">
      Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    </li>
    <li class="ais-InfiniteHits-item">
      Hit 4397400: Google - Chromecast - Black
    </li>
  </ol>

  <button class="ais-InfiniteHits-loadMore">Show more results</button>
</div>
```

#### Hierarchical menu

##### Options

| Before | After           |
| ------ | --------------- |
|        | `showMore`      |
|        | `showMoreLimit` |

##### CSS classes

| Before                                | After                                     |
| ------------------------------------- | ----------------------------------------- |
| `ais-hierarchical-menu`               | `ais-HierarchicalMenu`                    |
|                                       | `ais-HierarchicalMenu--noRefinement`      |
|                                       | `ais-HierarchicalMenu-searchBox`          |
| `ais-hierarchical-menu--list`         | `ais-HierarchicalMenu-list`               |
|                                       | `ais-HierarchicalMenu-list--child`        |
|                                       | `ais-HierarchicalMenu-list--lvl0`         |
|                                       | `ais-HierarchicalMenu-list--lvl1`         |
| `ais-hierarchical-menu--item`         | `ais-HierarchicalMenu-item`               |
| `ais-hierarchical-menu--item__active` | `ais-HierarchicalMenu-item--selected`     |
| `ais-hierarchical-menu--item__parent` | `ais-HierarchicalMenu-item--parent`       |
| `ais-hierarchical-menu--link`         | `ais-HierarchicalMenu-link`               |
| `ais-hierarchical-menu--label`        | `ais-HierarchicalMenu-label`              |
| `ais-hierarchical-menu--count`        | `ais-HierarchicalMenu-count`              |
| `ais-hierarchical-menu--noResults`    | `ais-HierarchicalMenu-noResults`          |
|                                       | `ais-HierarchicalMenu-showMore`           |
|                                       | `ais-HierarchicalMenu-showMore--disabled` |

##### Markup

###### Default

```html HTML icon=code-xml theme={"system"}
<div class="ais-HierarchicalMenu">
  <ul class="ais-HierarchicalMenu-list ais-HierarchicalMenu-list--lvl0">
    <li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent ais-HierarchicalMenu-item--selected">
      <a class="ais-HierarchicalMenu-link" href="#">
        <span class="ais-HierarchicalMenu-label">Appliances</span>
        <span class="ais-HierarchicalMenu-count">4,306</span>
      </a>
      <ul class="ais-HierarchicalMenu-list ais-HierarchicalMenu-list--child ais-HierarchicalMenu-list--lvl1">
        <li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent">
          <a class="ais-HierarchicalMenu-link" href="#">
            <span class="ais-HierarchicalMenu-label">Dishwashers</span>
            <span class="ais-HierarchicalMenu-count">181</span>
          </a>
        </li>
        <li class="ais-HierarchicalMenu-item">
          <a class="ais-HierarchicalMenu-link" href="#">
            <span class="ais-HierarchicalMenu-label">Fans</span>
            <span class="ais-HierarchicalMenu-count">91</span>
          </a>
        </li>
      </ul>
    </li>
    <li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent">
      <a class="ais-HierarchicalMenu-link" href="#">
        <span class="ais-HierarchicalMenu-label">Audio</span>
        <span class="ais-HierarchicalMenu-count">1,570</span>
      </a>
    </li>
  </ul>
  <button class="ais-HierarchicalMenu-showMore">Show more</button>
</div>
```

###### Show more disabled

```html HTML icon=code-xml theme={"system"}
<div class="ais-HierarchicalMenu">
  <ul class="ais-HierarchicalMenu-list ais-HierarchicalMenu-list--lvl0">
    <li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent ais-HierarchicalMenu-item--selected">
      <a class="ais-HierarchicalMenu-link" href="#">
        <span class="ais-HierarchicalMenu-label">Appliances</span>
        <span class="ais-HierarchicalMenu-count">4,306</span>
      </a>
      <ul class="ais-HierarchicalMenu-list ais-HierarchicalMenu-list--child ais-HierarchicalMenu-list--lvl1">
        <li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent">
          <a class="ais-HierarchicalMenu-link" href="#">
            <span class="ais-HierarchicalMenu-label">Dishwashers</span>
            <span class="ais-HierarchicalMenu-count">181</span>
          </a>
        </li>
        <li class="ais-HierarchicalMenu-item">
          <a class="ais-HierarchicalMenu-link" href="#">
            <span class="ais-HierarchicalMenu-label">Fans</span>
            <span class="ais-HierarchicalMenu-count">91</span>
          </a>
        </li>
      </ul>
    </li>
    <li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent">
      <a class="ais-HierarchicalMenu-link" href="#">
        <span class="ais-HierarchicalMenu-label">Audio</span>
        <span class="ais-HierarchicalMenu-count">1,570</span>
      </a>
    </li>
  </ul>
  <button class="ais-HierarchicalMenu-showMore ais-HierarchicalMenu-showMore--disabled" disabled>Show more</button>
</div>
```

#### `Menu`

##### Options

| Before                        | After                    |
| ----------------------------- | ------------------------ |
| `attributeName`               | `attribute`              |
| `showMore.limit`              | `showMoreLimit`          |
| `showMore.templates.active`   | `templates.showMoreText` |
| `showMore.templates.inactive` | `templates.showMoreText` |

* `showMore` is now a boolean option (`showMore.templates` are now in `templates`)
* `sortBy` defaults to `['isRefined', 'name:asc']`
* An object containing `isShowingMore` is passed to `showMoreText` template to toggle between the two states:

```js JavaScript icon=code theme={"system"}
{
  showMoreText: `
    {{#isShowingMore}}
      Show less
    {{/isShowingMore}}
    {{^isShowingMore}}
      Show more
    {{/isShowingMore}}
  `
}
```

##### CSS classes

| Before                   | After                         |
| ------------------------ | ----------------------------- |
| `ais-menu`               | `ais-Menu`                    |
| `ais-menu--list`         | `ais-Menu-list`               |
| `ais-menu--item`         | `ais-Menu-item`               |
| `ais-menu--item__active` | `ais-Menu-item--selected`     |
| `ais-menu--link`         | `ais-Menu-link`               |
|                          | `ais-Menu-label`              |
| `ais-menu--count`        | `ais-Menu-count`              |
|                          | `ais-Menu-noResults`          |
|                          | `ais-Menu-showMore`           |
|                          | `ais-Menu-showMore--disabled` |

##### Markup

###### Default

```html HTML icon=code-xml theme={"system"}
<div class="ais-Menu">
  <ul class="ais-Menu-list">
    <li class="ais-Menu-item ais-Menu-item--selected">
      <a class="ais-Menu-link" href="#">
        <span class="ais-Menu-label">Appliances</span>
        <span class="ais-Menu-count">4,306</span>
      </a>
    </li>
    <li class="ais-Menu-item">
      <a class="ais-Menu-link" href="#">
        <span class="ais-Menu-label">Audio</span>
        <span class="ais-Menu-count">1,570</span>
      </a>
    </li>
  </ul>
  <button class="ais-Menu-showMore">Show more</button>
</div>
```

###### Show more disabled

```html HTML icon=code-xml theme={"system"}
<div class="ais-Menu">
  <ul class="ais-Menu-list">
    <li class="ais-Menu-item ais-Menu-item--selected">
      <a class="ais-Menu-link" href="#">
        <span class="ais-Menu-label">Appliances</span>
        <span class="ais-Menu-count">4,306</span>
      </a>
    </li>
    <li class="ais-Menu-item">
      <a class="ais-Menu-link" href="#">
        <span class="ais-Menu-label">Audio</span>
        <span class="ais-Menu-count">1,570</span>
      </a>
    </li>
  </ul>
  <button class="ais-Menu-showMore ais-Menu-showMore--disabled" disabled>Show more</button>
</div>
```

#### Menu select

##### Options

| Before          | After       |
| --------------- | ----------- |
| `attributeName` | `attribute` |

##### CSS classes

| Before                    | After                          |
| ------------------------- | ------------------------------ |
| `ais-menu-select`         | `ais-MenuSelect`               |
|                           | `ais-MenuSelect--noRefinement` |
| `ais-menu-select--select` | `ais-MenuSelect-select`        |
| `ais-menu-select--option` | `ais-MenuSelect-option`        |
| `ais-menu-select--header` |                                |
| `ais-menu-select--footer` |                                |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-MenuSelect">
  <select class="ais-MenuSelect-select">
    <option class="ais-MenuSelect-option" value="Appliances">Appliances (4306)</option>
    <option class="ais-MenuSelect-option" value="Audio">Audio (1570)</option>
  </select>
</div>
```

#### `NumericMenu` (formerly `NumericRefinementListe`)

##### Options

| Before          | After       |
| --------------- | ----------- |
| `attributeName` | `attribute` |
| `options`       | `items`     |

The item `name` attribute is now named `label`.

##### CSS classes

| Before                              | After                            |
| ----------------------------------- | -------------------------------- |
| `ais-refinement-list`               | `ais-NumericMenu`                |
|                                     | `ais-NumericMenu--noRefinement`  |
| `ais-refinement-list--list`         | `ais-NumericMenu-list`           |
| `ais-refinement-list--item`         | `ais-NumericMenu-item`           |
| `ais-refinement-list--item__active` | `ais-NumericMenu-item--selected` |
| `ais-refinement-list--label`        | `ais-NumericMenu-label`          |
| `ais-refinement-list--radio`        | `ais-NumericMenu-radio`          |
|                                     | `ais-NumericMenu-labelText`      |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-NumericMenu">
  <ul class="ais-NumericMenu-list">
    <li class="ais-NumericMenu-item ais-NumericMenu-item--selected">
      <label class="ais-NumericMenu-label">
        <input class="ais-NumericMenu-radio" type="radio" name="NumericMenu" checked="" />
        <span class="ais-NumericMenu-labelText">All</span>
      </label>
    </li>
    <li class="ais-NumericMenu-item">
      <label class="ais-NumericMenu-label">
        <input class="ais-NumericMenu-radio" type="radio" name="NumericMenu" />
        <span class="ais-NumericMenu-labelText">Less than 500</span>
      </label>
    </li>
  </ul>
</div>
```

#### `NumericSelector`

Widget removed.

#### `Pagination`

##### Options

| Before            | After                  |
| ----------------- | ---------------------- |
| `maxPages`        | `totalPages`           |
| `showFirstLast`   | `showFirst` `showLast` |
|                   | `showNext`             |
|                   | `showPrevious`         |
| `labels`          | `templates`            |
| `labels.previous` | `templates.previous`   |
| `labels.next`     | `templates.next`       |
| `labels.first`    | `templates.first`      |
| `labels.last`     | `templates.last`       |

##### CSS classes

| Before                           | After                               |
| -------------------------------- | ----------------------------------- |
|                                  | `ais-Pagination`                    |
|                                  | `ais-Pagination--noRefinement`      |
| `ais-pagination`                 | `ais-Pagination-list`               |
| `ais-pagination--item`           | `ais-Pagination-item`               |
| `ais-pagination--item__first`    | `ais-Pagination-item--firstPage`    |
| `ais-pagination--item__last`     | `ais-Pagination-item--lastPage`     |
| `ais-pagination--item__previous` | `ais-Pagination-item--previousPage` |
| `ais-pagination--item__next`     | `ais-Pagination-item--nextPage`     |
|                                  | `ais-Pagination-item--page`         |
| `ais-pagination--item__active`   | `ais-Pagination-item--selected`     |
| `ais-pagination--item__disabled` | `ais-Pagination-item--disabled`     |
| `ais-pagination--link`           | `ais-Pagination-link`               |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-Pagination">
  <ul class="ais-Pagination-list">
    <li class="ais-Pagination-item ais-Pagination-item--firstPage ais-Pagination-item--disabled">
      <span class="ais-Pagination-link" aria-label="Previous">‹‹</span>
    </li>
    <li class="ais-Pagination-item ais-Pagination-item--previousPage ais-Pagination-item--disabled">
      <span class="ais-Pagination-link" aria-label="Previous">‹</span>
    </li>
    <li class="ais-Pagination-item ais-Pagination-item--selected">
      <a class="ais-Pagination-link" href="#">1</a>
    </li>
    <li class="ais-Pagination-item ais-Pagination-item--page">
      <a class="ais-Pagination-link" href="#">2</a>
    </li>
    <li class="ais-Pagination-item ais-Pagination-item--page">
      <a class="ais-Pagination-link" href="#">3</a>
    </li>
    <li class="ais-Pagination-item">
      <a class="ais-Pagination-link" href="#">4</a>
    </li>
    <li class="ais-Pagination-item ais-Pagination-item--nextPage">
      <a class="ais-Pagination-link" aria-label="Next" href="#">›</a>
    </li>
    <li class="ais-Pagination-item ais-Pagination-item--lastPage">
      <a class="ais-Pagination-link" aria-label="Next" href="#">››</a>
    </li>
  </ul>
</div>
```

#### `PriceRanges`

Widget removed.

#### `RangeInput`

##### Options

| Before             | After                     |
| ------------------ | ------------------------- |
| `attributeName`    | `attribute`               |
| `labels`           | `templates`               |
| `labels.separator` | `templates.separatorText` |
| `labels.submit`    | `templates.submitText`    |

##### CSS classes

| Before                       | After                          |
| ---------------------------- | ------------------------------ |
| `ais-range-input`            | `ais-RangeInput`               |
|                              | `ais-RangeInput--noRefinement` |
| `ais-range-input--body`      |                                |
| `ais-range-input--form`      | `ais-RangeInput-form`          |
| `ais-range-input--fieldset`  |                                |
|                              | `ais-RangeInput-label`         |
| `ais-range-input--labelMin`  |                                |
| `ais-range-input--labelMax`  |                                |
|                              | `ais-RangeInput-input`         |
| `ais-range-input--inputMin`  | `ais-RangeInput-input--min`    |
| `ais-range-input--inputMax`  | `ais-RangeInput-input--max`    |
| `ais-range-input--separator` | `ais-RangeInput-separator`     |
| `ais-range-input--submit`    | `ais-RangeInput-submit`        |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-RangeInput">
  <form class="ais-RangeInput-form">
    <label class="ais-RangeInput-label">
      <input class="ais-RangeInput-input ais-RangeInput-input--min" type="number" />
    </label>

    <span class="ais-RangeInput-separator">to</span>

    <label class="ais-RangeInput-label">
      <input class="ais-RangeInput-input ais-RangeInput-input--max" type="number" />
    </label>

    <button class="ais-RangeInput-submit" type="submit">Go</button>
  </form>
</div>
```

#### `RangeSlider`

##### Options

| Before          | After       |
| --------------- | ----------- |
| `attributeName` | `attribute` |

##### CSS classes

| Before                                | After                                |
| ------------------------------------- | ------------------------------------ |
| `ais-range-slider`                    | `ais-RangeSlider`                    |
| `ais-range-slider--disabled`          | `ais-RangeSlider--disabled`          |
| `ais-range-slider--handle`            | `ais-RangeSlider-handle`             |
| `ais-range-slider--tooltip`           | `ais-RangeSlider-tooltip`            |
| `ais-range-slider--value`             | `ais-RangeSlider-value`              |
| `ais-range-slider--marker-horizontal` | `ais-RangeSlider-marker--horizontal` |
| `ais-range-slider--marker-large`      | `ais-RangeSlider-marker--large`      |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-RangeSlider">
  <div class="rheostat rheostat-horizontal" style="position: relative;">
    <div class="rheostat-background"></div>
    <div class="rheostat-handle rehostat-handle--lower" aria-valuemax="5000" aria-valuemin="1" aria-valuenow="750" aria-disabled="false" data-handle-key="0" role="slider" tabindex="0" style="left: 15%; position: absolute;">
      <div class="rheostat-tooltip">$750</div>
    </div>
    <div class="rheostat-handle rheostat-handle--upper" aria-valuemax="5000" aria-valuemin="750" aria-valuenow="5000" aria-disabled="false" data-handle-key="1" role="slider" tabindex="0" style="left: 100%; position: absolute;">
      <div class="rheostat-tooltip">$5,000</div>
    </div>
    <div class="rheostat-progress" style="left: 15%; width: 85%;"></div>
    <div class="rheostat-marker rheostat-marker--large" style="left: 0%; position: absolute; margin-left: 0px;">
      <div class="rheostat-value">1</div>
    </div>
    <div class="rheostat-marker" style="left: 2.94118%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 5.88235%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 8.82353%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 11.7647%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 14.7059%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 17.6471%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 20.5882%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 23.5294%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 26.4706%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 29.4118%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 32.3529%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 35.2941%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 38.2353%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 41.1765%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 44.1176%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 47.0588%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker rheostat-marker--large" style="left: 50%; position: absolute; margin-left: 0px;">
      <div class="rheostat-value">2,500</div>
    </div>
    <div class="rheostat-marker" style="left: 52.9412%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 55.8824%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 58.8235%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 61.7647%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 64.7059%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 67.6471%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 70.5882%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 73.5294%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 76.4706%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 79.4118%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 82.3529%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 85.2941%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 88.2353%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 91.1765%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 94.1176%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker" style="left: 97.0588%; position: absolute; margin-left: 0px;"></div>
    <div class="rheostat-marker rheostat-marker--large" style="left: 100%; position: absolute; margin-left: -1px;">
      <div class="rheostat-value">5,000</div>
    </div>
  </div>
</div>
```

#### `RatingMenu` (formerly `StarRating`)

##### Options

| Before          | After       |
| --------------- | ----------- |
| `attributeName` | `attribute` |
| `labels.andUp`  | Removed     |

The value for the label `andUp` is now inline inside `templates.item`.

##### CSS classes

| Before                            | After                            |
| --------------------------------- | -------------------------------- |
| `ais-star-rating`                 | `ais-RatingMenu`                 |
| `ais-star-rating--list`           | `ais-RatingMenu-list`            |
| `ais-star-rating--item`           | `ais-RatingMenu-item`            |
| `ais-star-rating--item__active`   | `ais-RatingMenu-item--selected`  |
| `ais-star-rating--item__disabled` | `ais-RatingMenu-item--disabled`  |
| `ais-star-rating--link`           | `ais-RatingMenu-link`            |
| `ais-star-rating--star`           | `ais-RatingMenu-starIcon`        |
|                                   | `ais-RatingMenu-starIcon--full`  |
| `ais-star-rating--star__empty`    | `ais-RatingMenu-starIcon--empty` |
| `ais-star-rating--count`          | `ais-RatingMenu-count`           |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-RatingMenu">
  <svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
    <symbol id="ais-RatingMenu-starSymbol" viewBox="0 0 24 24"><path d="M12 .288l2.833 8.718h9.167l-7.417 5.389 2.833 8.718-7.416-5.388-7.417 5.388 2.833-8.718-7.416-5.389h9.167z"/></symbol>
    <symbol id="ais-RatingMenu-starEmptySymbol" viewBox="0 0 24 24"><path d="M12 6.76l1.379 4.246h4.465l-3.612 2.625 1.379 4.246-3.611-2.625-3.612 2.625 1.379-4.246-3.612-2.625h4.465l1.38-4.246zm0-6.472l-2.833 8.718h-9.167l7.416 5.389-2.833 8.718 7.417-5.388 7.416 5.388-2.833-8.718 7.417-5.389h-9.167l-2.833-8.718z"/></symbol>
  </svg>
  <ul class="ais-RatingMenu-list">
    <li class="ais-RatingMenu-item ais-RatingMenu-item--disabled">
      <div class="ais-RatingMenu-link" aria-label="5 & up" disabled>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <span class="ais-RatingMenu-label" aria-hidden="true">& Up</span>
        <span class="ais-RatingMenu-count">2,300</span>
      </div>
    </li>
    <li class="ais-RatingMenu-item ais-RatingMenu-item--selected">
      <a class="ais-RatingMenu-link" aria-label="4 & up" href="#">
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--empty" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starEmptySymbol"></use></svg>
        <span class="ais-RatingMenu-label" aria-hidden="true">& Up</span>
        <span class="ais-RatingMenu-count">2,300</span>
      </a>
    </li>
    <li class="ais-RatingMenu-item">
      <a class="ais-RatingMenu-link" aria-label="3 & up" href="#">
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--full" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starSymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--empty" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starEmptySymbol"></use></svg>
        <svg class="ais-RatingMenu-starIcon ais-RatingMenu-starIcon--empty" aria-hidden="true" width="24" height="24"><use xlink:href="#ais-RatingMenu-starEmptySymbol"></use></svg>
        <span class="ais-RatingMenu-label" aria-hidden="true">& Up</span>
        <span class="ais-RatingMenu-count">1,750</span>
      </a>
    </li>
  </ul>
</div>
```

#### `RefinementList`

##### Options

| Before                                     | After                           |
| ------------------------------------------ | ------------------------------- |
| `attributeName`                            | `attribute`                     |
| `searchForFacetValues`                     | `searchable`                    |
| `searchForFacetValues.placeholder`         | `searchablePlaceholder`         |
| `searchForFacetValues.isAlwaysActive`      | `searchableIsAlwaysActive`      |
| `searchForFacetValues.escapeFacetValues`   | `searchableEscapeFacetValues`   |
| `searchForFacetValues.templates.noResults` | `templates.searchableNoResults` |
| `showMore.templates.active`                | `templates.showMoreText`        |
| `showMore.templates.inactive`              | `templates.showMoreText`        |

* `searchablePlaceholder` defaults to `"Search..."`
* `searchableEscapeFacetValues` defaults to `true`
* `searchableIsAlwaysActive` defaults to `true`
* `showMore` is now a boolean option (`searchForFacetValues.templates` and `showMore.templates` are now in `templates`)
* An object containing `isShowingMore` is passed to `showMoreText` template to toggle between the two states:

```js JavaScript icon=code theme={"system"}
{
  showMoreText: `
    {{#isShowingMore}}
      Show less
    {{/isShowingMore}}
    {{^isShowingMore}}
      Show more
    {{/isShowingMore}}
  `
}
```

##### CSS classes

| Before                              | After                                   |
| ----------------------------------- | --------------------------------------- |
| `ais-refinement-list`               | `ais-RefinementList`                    |
|                                     | `ais-RefinementList--noRefinement`      |
|                                     | `ais-RefinementList-noResults`          |
| `ais-refinement-list--header`       |                                         |
| `ais-refinement-list--body`         |                                         |
| `ais-refinement-list--footer`       |                                         |
| `ais-refinement-list--list`         | `ais-RefinementList-list`               |
| `ais-refinement-list--item`         | `ais-RefinementList-item`               |
| `ais-refinement-list--item__active` | `ais-RefinementList-item--selected`     |
| `ais-refinement-list--label`        | `ais-RefinementList-label`              |
| `ais-refinement-list--checkbox`     | `ais-RefinementList-checkbox`           |
|                                     | `ais-RefinementList-labelText`          |
| `ais-refinement-list--count`        | `ais-RefinementList-count`              |
|                                     | `ais-RefinementList-showMore`           |
|                                     | `ais-RefinementList-showMore--disabled` |

##### Markup

###### Default

```html HTML icon=code-xml theme={"system"}
<div class="ais-RefinementList">
  <div class="ais-RefinementList-searchBox">
    <!-- SearchBox widget here -->
  </div>
  <ul class="ais-RefinementList-list">
    <li class="ais-RefinementList-item ais-RefinementList-item--selected">
      <label class="ais-RefinementList-label">
        <input class="ais-RefinementList-checkbox" type="checkbox" value="Insignia™" checked="" />
        <span class="ais-RefinementList-labelText">Insignia™</span>
        <span class="ais-RefinementList-count">746</span>
      </label>
    </li>
    <li class="ais-RefinementList-item">
      <label class="ais-RefinementList-label">
        <input class="ais-RefinementList-checkbox" type="checkbox" value="Samsung">
        <span class="ais-RefinementList-labelText">Samsung</span>
        <span class="ais-RefinementList-count">633</span>
      </label>
    </li>
  </ul>
  <button class="ais-RefinementList-showMore">Show more</button>
</div>
```

###### Show more disabled

```html HTML icon=code-xml theme={"system"}
<div class="ais-RefinementList">
  <div class="ais-RefinementList-searchBox">
    <!-- SearchBox widget here -->
  </div>
  <ul class="ais-RefinementList-list">
    <li class="ais-RefinementList-item ais-RefinementList-item--selected">
      <label class="ais-RefinementList-label">
        <input class="ais-RefinementList-checkbox" type="checkbox" value="Insignia™" checked="" />
        <span class="ais-RefinementList-labelText">Insignia™</span>
        <span class="ais-RefinementList-count">746</span>
      </label>
    </li>
    <li class="ais-RefinementList-item">
      <label class="ais-RefinementList-label">
        <input class="ais-RefinementList-checkbox" type="checkbox" value="Samsung">
        <span class="ais-RefinementList-labelText">Samsung</span>
        <span class="ais-RefinementList-count">633</span>
      </label>
    </li>
  </ul>
  <button class="ais-RefinementList-showMore ais-RefinementList-showMore--disabled" disabled>Show more</button>
</div>
```

###### With search and no results

```html HTML icon=code-xml theme={"system"}
<div class="ais-RefinementList">
  <div class="ais-RefinementList-searchBox">
    <div class="ais-SearchBox">
      <form class="ais-SearchBox-form" novalidate>
        <input class="ais-SearchBox-input" autocomplete="off" autocorrect="off" autocapitalize="off" placeholder="Search for products" spellcheck="false" maxlength="512" type="search" value="" />
        <button class="ais-SearchBox-submit" type="submit" title="Submit the search query.">
          <svg class="ais-SearchBox-submitIcon" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 40 40">
            <path d="M26.804 29.01c-2.832 2.34-6.465 3.746-10.426 3.746C7.333 32.756 0 25.424 0 16.378 0 7.333 7.333 0 16.378 0c9.046 0 16.378 7.333 16.378 16.378 0 3.96-1.406 7.594-3.746 10.426l10.534 10.534c.607.607.61 1.59-.004 2.202-.61.61-1.597.61-2.202.004L26.804 29.01zm-10.426.627c7.323 0 13.26-5.936 13.26-13.26 0-7.32-5.937-13.257-13.26-13.257C9.056 3.12 3.12 9.056 3.12 16.378c0 7.323 5.936 13.26 13.258 13.26z"></path>
          </svg>
        </button>
        <button class="ais-SearchBox-reset" type="reset" title="Clear the search query." hidden>
          <svg class="ais-SearchBox-resetIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="10" height="10">
            <path d="M8.114 10L.944 2.83 0 1.885 1.886 0l.943.943L10 8.113l7.17-7.17.944-.943L20 1.886l-.943.943-7.17 7.17 7.17 7.17.943.944L18.114 20l-.943-.943-7.17-7.17-7.17 7.17-.944.943L0 18.114l.943-.943L8.113 10z"></path>
          </svg>
        </button>
        <span class="ais-SearchBox-loadingIndicator" hidden>
          <svg width="16" height="16" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#444" class="ais-SearchBox-loadingIcon">
            <g fill="none" fillRule="evenodd">
              <g transform="translate(1 1)" strokeWidth="2">
                <circle stroke-opacity=".5" cx="18" cy="18" r="18" />
                <path d="M36 18c0-9.94-8.06-18-18-18">
                  <animateTransform
                    attributeName="transform"
                    type="rotate"
                    from="0 18 18"
                    to="360 18 18"
                    dur="1s"
                    repeatCount="indefinite"
                  />
                </path>
              </g>
            </g>
          </svg>
        </span>
      </form>
    </div>
  </div>
  <div class="ais-RefinementList-noResults">No results.</div>
</div>
```

#### `SearchBox`

##### Options

| Before                      | After                                    |
| --------------------------- | ---------------------------------------- |
| `poweredBy`                 | use the `poweredBy` widget               |
| `wrapInput`                 | use the `connectSearchBox` connector     |
| `searchOnEnterKeyPressOnly` | `searchAsYouType` (default: `true`)      |
| `reset`                     | `showReset`                              |
| `magnifier`                 | `showSubmit`                             |
| `loadingIndicator`          | `showLoadingIndicator` (default: `true`) |

With the drop of `wrapInput`, it was decided not to accept `input`s as containers anymore.
If you want complete control over the rendering, use the [`connectSearchBox`](/doc/api-reference/widgets/search-box/js#customize-the-ui-with-connectsearchbox) connector.

The search box doesn't support [`poweredBy`](/doc/api-reference/widgets/powered-by/js).
[Algolia requires](https://www.algolia.com/policies/free-services-terms/) that you use this widget if you're on a community plan (open source, not-for-profit, or [DocSearch](https://docsearch.algolia.com/)).

Configuration options for `reset`, `submit` and `loadingIndicator` have been moved to `templates` and `cssClasses`. For example, for `reset`:

* `reset.template` => `templates.reset`
* `reset.cssClasses.root` => `cssClasses.reset`

`autofocus` is now set to `false` by default and doesn't support the `"auto"` value anymore.

##### CSS classes

| Before                                      | After                            |
| ------------------------------------------- | -------------------------------- |
| `ais-search-box`                            | `ais-SearchBox`                  |
|                                             | `ais-SearchBox-form`             |
| `ais-search-box--input`                     | `ais-SearchBox-input`            |
| `ais-search-box--magnifier-wrapper`         |                                  |
| `ais-search-box--magnifier`                 | `ais-SearchBox-submit`           |
|                                             | `ais-SearchBox-submitIcon`       |
| `ais-search-box--reset-wrapper`             |                                  |
| `ais-search-box--reset`                     | `ais-SearchBox-reset`            |
|                                             | `ais-SearchBox-resetIcon`        |
| `ais-search-box--loading-indicator-wrapper` |                                  |
| `ais-search-box--loading-indicator`         | `ais-SearchBox-loadingIndicator` |
|                                             | `ais-SearchBox-loadingIcon`      |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-SearchBox">
  <form class="ais-SearchBox-form" novalidate>
    <input class="ais-SearchBox-input" autocomplete="off" autocorrect="off" autocapitalize="off" placeholder="Search for products" spellcheck="false" maxlength="512" type="search" value="" />
    <button class="ais-SearchBox-submit" type="submit" title="Submit the search query.">
      <svg class="ais-SearchBox-submitIcon" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 40 40">
        <path d="M26.804 29.01c-2.832 2.34-6.465 3.746-10.426 3.746C7.333 32.756 0 25.424 0 16.378 0 7.333 7.333 0 16.378 0c9.046 0 16.378 7.333 16.378 16.378 0 3.96-1.406 7.594-3.746 10.426l10.534 10.534c.607.607.61 1.59-.004 2.202-.61.61-1.597.61-2.202.004L26.804 29.01zm-10.426.627c7.323 0 13.26-5.936 13.26-13.26 0-7.32-5.937-13.257-13.26-13.257C9.056 3.12 3.12 9.056 3.12 16.378c0 7.323 5.936 13.26 13.258 13.26z"></path>
      </svg>
    </button>
    <button class="ais-SearchBox-reset" type="reset" title="Clear the search query." hidden>
      <svg class="ais-SearchBox-resetIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="10" height="10">
        <path d="M8.114 10L.944 2.83 0 1.885 1.886 0l.943.943L10 8.113l7.17-7.17.944-.943L20 1.886l-.943.943-7.17 7.17 7.17 7.17.943.944L18.114 20l-.943-.943-7.17-7.17-7.17 7.17-.944.943L0 18.114l.943-.943L8.113 10z"></path>
      </svg>
    </button>
    <span class="ais-SearchBox-loadingIndicator" hidden>
      <svg width="16" height="16" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#444" class="ais-SearchBox-loadingIcon">
        <g fill="none" fillRule="evenodd">
          <g transform="translate(1 1)" strokeWidth="2">
            <circle strokeOpacity=".5" cx="18" cy="18" r="18" />
            <path d="M36 18c0-9.94-8.06-18-18-18">
              <animateTransform
                attributeName="transform"
                type="rotate"
                from="0 18 18"
                to="360 18 18"
                dur="1s"
                repeatCount="indefinite"
              />
            </path>
          </g>
        </g>
      </svg>
    </span>
  </form>
<div>
```

#### `SortBy`

##### Options

| Before    | After   |
| --------- | ------- |
| `indices` | `items` |

* A `sortBy` item value is now `value` instead of `name`:

```js JavaScript icon=code theme={"system"}
const sortByItem = {
  value: string,
  label: string,
};
```

##### CSS classes

| Before                 | After               |
| ---------------------- | ------------------- |
|                        | `ais-SortBy`        |
| `ais-sort-by-selector` | `ais-SortBy-select` |
| `ais-sort-by--item`    | `ais-SortBy-option` |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-SortBy">
 <select class="ais-SortBy-select">
   <option class="ais-SortBy-option" value="Most relevant">Most relevant</option>
   <option class="ais-SortBy-option" value="Lowest price">Lowest price</option>
 </select>
</div>
```

#### Stats

##### CSS classes

| Before            | After            |
| ----------------- | ---------------- |
| `ais-stats`       | `ais-Stats`      |
| `ais-stats--body` |                  |
| `ais-stats--time` |                  |
|                   | `ais-Stats-text` |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-Stats">
  <span class="ais-Stats-text">20,337 results found in 1ms.</span>
</div>
```

#### `ToggleRefinement` (formerly `Toggle`)

##### Options

| Before              | After                 |
| ------------------- | --------------------- |
| `attributeName`     | `attribute`           |
| `collapsible`       |                       |
| `autoHideContainer` |                       |
| `label`             | `templates.labelText` |
| `templates.item`    |                       |
| `values.on`         | `on`                  |
| `values.off`        | `off`                 |

`collapsible` and `autoHideContainer` options have been removed. These options are now implemented as part of the Panel widget wrapper.

The `label` options has been moved into the `templates.labelText` template to make it consistent with the templates parameters of other widgets and the `item` template was removed.
Data that was provided to `templates.item` is now provided to `templates.labelText`.
If your <Index /> attribute is called `free_shipping`, the default template displays "free\_shipping".
To rename it, change `templates.labelText` to "Free shipping".

##### CSS classes

| Before                 | After                            |
| ---------------------- | -------------------------------- |
| `ais-toggle`           | `ais-ToggleRefinement`           |
| `ais-toggle--list`     |                                  |
| `ais-toggle--item`     |                                  |
|                        | `ais-ToggleRefinement-label`     |
| `ais-toggle--checkbox` | `ais-ToggleRefinement-checkbox`  |
| `ais-toggle--label`    | `ais-ToggleRefinement-labelText` |

##### Markup

```html HTML icon=code-xml theme={"system"}
<div class="ais-ToggleRefinement">
  <label class="ais-ToggleRefinement-label">
    <input class="ais-ToggleRefinement-checkbox" type="checkbox" value="Free Shipping" />
    <span class="ais-ToggleRefinement-labelText">Free Shipping</span>
  </label>
</div>
```

### Connectors

#### `connectAutocomplete`

##### Options

| Before       | After        |
| ------------ | ------------ |
| `escapeHits` | `escapeHTML` |

* `escapeHTML` becomes `true` by default.

#### `connectBreadcrumb`

* The BreadcrumbItem `name` property is renamed to `label`.

#### `connectGeoSearch`

##### Options

| Before                    | After                                                  |
| ------------------------- | ------------------------------------------------------ |
| `paddingBoundingBox`      | Removed                                                |
| `enableGeolocationWithIP` | Removed - use the Configure widget instead (see below) |
| `position`                | Removed - use the Configure widget instead (see below) |
| `radius`                  | Removed - use the Configure widget instead (see below) |
| `precision`               | Removed - use the Configure widget instead (see below) |

Since `paddingBoundingBox` conflicted with the `routing` option it was removed to support URLSync for the GeoSearch widget.

##### `enableGeolocationWithIP`

**Before:**

```js JavaScript icon=code theme={"system"}
const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
  // Render implementation
});

customGeoSearch({
  enableGeolocationWithIP: true,
});
```

**After:**

```js JavaScript icon=code theme={"system"}
const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
  // Render implementation
});

instantsearch.widgets.configure({
  aroundLatLngViaIP: true,
});

customGeoSearch();
```

##### `position`

**Before:**

```js JavaScript icon=code theme={"system"}
const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
  // Render implementation
});

customGeoSearch({
  position: { lat: 40.71, lng: -74.01 },
});
```

**After:**

```js JavaScript icon=code theme={"system"}
const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
  // Render implementation
});

instantsearch.widgets.configure({
  aroundLatLng: '40.71, -74.01',
});

customGeoSearch();
```

##### `radius`

**Before:**

```js JavaScript icon=code theme={"system"}
const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
  // Render implementation
});

customGeoSearch({
  radius: 1000,
});
```

**After:**

```js JavaScript icon=code theme={"system"}
const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
  // Render implementation
});

instantsearch.widgets.configure({
  aroundRadius: 1000,
});

customGeoSearch();
```

##### `precision`

**Before:**

```js JavaScript icon=code theme={"system"}
const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
  // Render implementation
});

customGeoSearch({
  precision: 1000,
});
```

**After:**

```js JavaScript icon=code theme={"system"}
const customGeoSearch = instantsearch.connectors.connectGeoSearch(() => {
  // Render implementation
});

instantsearch.widgets.configure({
  aroundPrecision: 1000,
});

customGeoSearch();
```

#### `connectRange`

##### Options

| Before          | After       |
| --------------- | ----------- |
| `attributeName` | `attribute` |

#### `connectRangeSlider`

Connector removed (use `connectRange` instead).

#### `connectClearRefinements` (formerly `connectClearAll`)

##### Options

| Before              | After                |
| ------------------- | -------------------- |
| `excludeAttributes` | `excludedAttributes` |

#### `connectNumericMenu` (formerly `connectNumericRefinementList`)

##### Options

| Before          | After       |
| --------------- | ----------- |
| `attributeName` | `attribute` |
| `options`       | `items`     |

* `name` becomes `label`

#### `connectRefinementList`

##### Options

| Before          | After       |
| --------------- | ----------- |
| `attributeName` | `attribute` |

* `escapeFacetValues` defaults to `true`

#### `connectCurrentRefinements` (used to be `connectCurrentRefinedValues`)

##### Options

| Before        | After                    |
| ------------- | ------------------------ |
| `clearsQuery` | `excludedAttributes: []` |

##### `items` (used to be refinements )

`refinements` used to be a flat list, now it's called `items` (one per attribute), which each has its own list of `refinements`

## Upgrade from v1 to v2

This guide has all the major changes that introduced in v2 with the description on how to
migrate from v1.

### The `searchBox` widget has new default options

To identify the input as a search box, a magnifying glass icon was placed at the start, and a reset button displayed as a cross at the end.

You can customize the result with these two options:

* **reset** `boolan|{template?: string|Function, cssClasses?: {root: string}}`
  Display a reset button in the input when there is a query.
* **magnifier** `boolan|{template?: string|Function, cssClasses?: {root: string}}`
  Display a magnifier should at beginning of the input.

To make the search box like in v1, you can do the following:

```js JavaScript icon=code theme={"system"}
const search = instantsearch(/* Your parameters here */);
search.addWidgets([
  instantsearch.widgets.searchbox({
    container: '#your-search-input',
    reset: false,
    magnifier: false,
  })
]);
```

You can read more about these options on the [`searchBox`](/doc/api-reference/widgets/search-box/js) API reference.

### No more `hitsPerPage` in `hits` and `infiniteHits`

This option was removed from those two widgets. To configure
this option of the engine, there are still three ways:

* Use the [dashboard](https://dashboard.algolia.com/explorer/configuration/pagination) or
  the [client](/doc/libraries/sdk/v1/methods/set-settings),
  to change the setting at the index level.
* Use the [`hitsPerPage`](/doc/api-reference/widgets/hits-per-page/js) widget.
* Use the configuration option of `instantsearch`:

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  // ... do not forget the credentials
  searchParameters: {
    hitsPerPage: 42,
  }
});
```

### The items aren't sorted like before in the refinementList / menu

The default sort order of those widgets has changed. This might have affected your implementation
if you didn't specify them originally. To change back the order use the `sortBy` configuration
key.

Here are examples of usage of `sortBy` using the previous sorting scheme:

```js JavaScript icon=code theme={"system"}
const yourSearch = instantsearch(/* parameters */);
yourSearch.addWidgets([
  instantsearch.widgets.refinementList({
    container: '#brands',
    attributeName: 'brand',
    // now the default is ['isRefined', 'count:desc', 'name:asc']
    sortBy: ['count:desc', 'name:asc'],
  }),

  instantsearch.widgets.menu({
    container: '#categories',
    attributeName: 'categories',
    // now the default is ['name:asc']
    sortBy: ['count:desc', 'name:asc']
  })
]);
```

If you want to learn more about sorting the values, check out the widget API to see what are
the valid values for the `sortBy` option of [`menu`](/doc/api-reference/widgets/menu/js) or
[`refinementList`](/doc/api-reference/widgets/refinement-list/js)

### Some variables have been changed

Internally all the widgets are now using the connectors. The aim was to ensure the API
was as close to the one offered by
[`react-instantsearch connectors`](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#customize-the-complete-ui-of-the-widgets).
This then affected the name of some variables in the templates and the API.

Changes in templates:

* In the item template of the hierarchicalMenu and menu widgets, `name` becomes `label`
* In the item template of the refinementList widget, `name` becomes `value`.

Changes in the API:

* In `hitsPerPageSelector`, the `options` was renamed to `items`.

### React components can't be used as templates

When InstantSearch.js was created it was built using React and it wasn't known that react-instantsearch would be built.
[`react-instantsearch`](/doc/guides/building-search-ui/what-is-instantsearch/react) is now the recommended solution if your app uses React.
That's why support for the React based templates was dropped in this release.

Algolia considers the engine used to build the widgets in InstantSearch.js to be an implementation detail. Since it isn't exposed anymore, Algolia can change it and use the best solution for each release.

### `rangeSlider` widget is using Rheostat as Slider Component

Slider components are hard to implement and that's why Algolia relies on an external
component for that. Algolia is taking the opportunity of this new version
to switch to the current 'state of the art' of sliders: [Rheostat](https://github.com/airbnb/rheostat).

If you want to customize the style, some CSS classes have changed.

The look is still similar as the one in the V1.

### `searchFunction` can be used to change parameters

{/* vale Google.Headings = YES */}

Introduced in 1.3.0, `searchFunction` was originally meant as a way to change
the timing of the search. However it was realized that it was a good way to
alter the search state before making the actual state.

This is what's required to force the query string:

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  /* other parameters */
  searchFunction(helper) {
    search.helper.setQuery('fixed query');
    helper.search();
  }
});
```

And now, it's more straightforward:

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  /* other parameters */
  searchFunction(helper) {
    helper.setQuery('fixed query').search();
  }
});
```

The helper [still resets the page to 0](https://community.algolia.com/algoliasearch-helper-js/concepts.html#smart-page-behavior)
when the parameters change.
To keep the page you set, do the following:

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  /* other parameters */
  searchFunction(helper) {
    const p = helper.getPage();
    helper.setQuery('fixed query')
          .setPage(p)
          .search();
  }
});
```
