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

# dynamicWidgets

> Renders matching refinement widgets in the facet order returned by Algolia.

export const DynamicFacets = ({current}) => <div>
    <p>
      This widget displays matching refinement widgets in the order provided by{" "}
      <a className="link" href="/doc/api-reference/api-parameters/renderingContent#param-facet-ordering">
        <code>renderingContent.facetOrdering</code>
      </a>{" "}
      in the search response. 
    </p>

    <p>
      Configure the order with{" "}
      <a className="link" href={`/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/${current}`}>
        facet display
      </a>
      , override it with{" "}
      <a className="link" href="/doc/guides/managing-results/rules/rules-overview">
        Algolia Rules
      </a>
      , or generate it automatically with{" "}
      <a className="link" href="/doc/guides/algolia-ai/dynamic-facets">
        Dynamic Facets
      </a>
      . Dynamic Facets orders facets and facet values based on recent search
      data.
    </p>
  </div>;

export const FlavorSwitcher = ({current, baseHref = "", options = [], label = "InstantSearch framework"}) => {
  if (options.length === 0) {
    return <div className="not-prose" role="alert" style={{
      margin: "0.25rem 0 1.5rem",
      padding: "0.75rem",
      border: "1px solid #f59e0b",
      borderRadius: "0.625rem",
      color: "inherit",
      fontSize: "0.875rem"
    }}>
        FlavorSwitcher requires at least one option.
      </div>;
  }
  const selected = options.find(option => option.value === current) ?? options[0];
  return <div className="not-prose mint-flavor-switcher">
      <style>{`
        .mint-flavor-switcher {
          --mfs-bg: #ffffff;
          --mfs-bg-hover: #f4f4f5;
          --mfs-bg-current: #eef2ff;
          --mfs-border: #d4d4d8;
          --mfs-fg: #18181b;
          --mfs-muted: #71717a;
          --mfs-accent: #4f46e5;
          position: relative;
          width: min(100%, 19rem);
          margin: 0.25rem 0 1.5rem;
          color: var(--mfs-fg);
          font-size: 0.875rem;
          line-height: 1.25rem;
        }

        .dark .mint-flavor-switcher {
          --mfs-bg: #18181b;
          --mfs-bg-hover: #27272a;
          --mfs-bg-current: #272747;
          --mfs-border: #3f3f46;
          --mfs-fg: #fafafa;
          --mfs-muted: #a1a1aa;
          --mfs-accent: #a5b4fc;
        }

        .mint-flavor-switcher details {
          position: relative;
        }

        .mint-flavor-switcher summary {
          display: flex;
          min-height: 2.75rem;
          box-sizing: border-box;
          align-items: center;
          justify-content: space-between;
          gap: 0.75rem;
          padding: 0.625rem 0.75rem;
          border: 1px solid var(--mfs-border);
          border-radius: 0.625rem;
          background: var(--mfs-bg);
          color: var(--mfs-fg);
          cursor: pointer;
          font-weight: 600;
          list-style: none;
          transition: border-color 150ms ease, box-shadow 150ms ease;
        }

        .mint-flavor-switcher summary::-webkit-details-marker {
          display: none;
        }

        .mint-flavor-switcher summary:hover {
          border-color: var(--mfs-accent);
        }

        .mint-flavor-switcher summary:focus-visible {
          outline: 2px solid var(--mfs-accent);
          outline-offset: 2px;
        }

        .mint-flavor-switcher__label {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
        }

        .mint-flavor-switcher__chevron {
          flex: none;
          transition: transform 150ms ease;
        }

        .mint-flavor-switcher details[open] .mint-flavor-switcher__chevron {
          transform: rotate(180deg);
        }

        .mint-flavor-switcher__menu {
          position: absolute;
          z-index: 50;
          top: calc(100% + 0.375rem);
          left: 0;
          width: 100%;
          box-sizing: border-box;
          margin: 0;
          padding: 0.375rem;
          border: 1px solid var(--mfs-border);
          border-radius: 0.625rem;
          background: var(--mfs-bg);
          box-shadow: 0 12px 30px rgb(0 0 0 / 16%);
          list-style: none;
        }

        .mint-flavor-switcher__menu li {
          margin: 0;
          padding: 0;
        }

        .mint-flavor-switcher__option {
          display: grid;
          gap: 0.125rem;
          padding: 0.625rem 0.75rem;
          border-radius: 0.4rem;
          color: var(--mfs-fg);
          text-decoration: none;
        }

        .mint-flavor-switcher__option:hover {
          background: var(--mfs-bg-hover);
        }

        .mint-flavor-switcher__option:focus-visible {
          outline: 2px solid var(--mfs-accent);
          outline-offset: -2px;
        }

        .mint-flavor-switcher__option[aria-current="page"] {
          background: var(--mfs-bg-current);
          color: var(--mfs-accent);
        }

        .mint-flavor-switcher__name {
          font-weight: 600;
        }

        .mint-flavor-switcher__description {
          color: var(--mfs-muted);
          font-size: 0.8125rem;
        }

        @media (prefers-reduced-motion: reduce) {
          .mint-flavor-switcher summary,
          .mint-flavor-switcher__chevron {
            transition: none;
          }
        }
      `}</style>

      <details>
        <summary aria-label={`${label}: ${selected.label}`}>
          <span className="mint-flavor-switcher__label">{selected.label}</span>
          <svg className="mint-flavor-switcher__chevron" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="m6 9 6 6 6-6" />
          </svg>
        </summary>

        <ul className="mint-flavor-switcher__menu" aria-label={label}>
          {options.map(option => {
    const isCurrent = option.value === selected.value;
    const href = option.href ?? `${baseHref.replace(/\/$/, "")}/${encodeURIComponent(option.value)}`;
    return <li key={option.value}>
                <a className="mint-flavor-switcher__option" href={href} aria-current={isCurrent ? "page" : undefined}>
                  <span className="mint-flavor-switcher__name">
                    {option.label}
                  </span>
                  {option.description ? <span className="mint-flavor-switcher__description">
                      {option.description}
                    </span> : null}
                </a>
              </li>;
  })}
        </ul>
      </details>
    </div>;
};

<div className="mint-flavor-switcher-slot not-prose">
  <FlavorSwitcher
    current="js"
    baseHref="/doc/api-reference/widgets/dynamic-widgets"
    options={[
{ value: "js", label: "JavaScript", description: "InstantSearch.js" },
{ value: "react", label: "React", description: "React InstantSearch" },
{ value: "vue", label: "Vue", description: "Vue InstantSearch" },
{ value: "ios", label: "iOS", description: "InstantSearch iOS" },
{ value: "android", label: "Android", description: "InstantSearch Android" },
]}
  />
</div>

```ts Signature theme={"system"}
dynamicWidgets({
  container: string | HTMLElement,
  widgets: function[],
  // Optional parameters
  transformItems?: function,
  fallbackWidget?: function,
  facets?: ['*']|[],
  maxValuesPerFacet?: number,
});
```

## Import

<CodeGroup>
  ```js Package manager theme={"system"}
  import { dynamicWidgets } from "instantsearch.js/es/widgets";
  ```

  ```js CDN theme={"system"}
  const { dynamicWidgets } = instantsearch.widgets;
  // Use instantsearch.widgets.dynamicWidgets()
  ```
</CodeGroup>

<Card title="See this widget in action" icon="monitor-play" href="https://instantsearchjs.netlify.app/stories/js/?path=/story/basics-dynamicWidgets--default" horizontal>
  Preview this widget and its behavior.
</Card>

## About this widget

<DynamicFacets current="js" />

### Requirements

Declare each facet attribute in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting).

Configure the facet order in the [Algolia dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or
with [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering).

By default, `dynamicWidgets` requests all facets with `facets: ["*"]` and requests up to 20 values per facet.
These defaults help avoid a second request after the matching widgets mount.

Override these defaults with [`facets`](#param-facets) and [`maxValuesPerFacet`](#param-max-values-per-facet).

<Note>
  `dynamicWidgets` requires InstantSearch.js 4.72.0 or later.
</Note>

## Examples

<CodeGroup>
  ```js Default theme={"system"}
  dynamicWidgets({
    container: "#dynamic-widgets",
    widgets: [
      (container) => refinementList({ container, attribute: "brand" }),
      (container) =>
        hierarchicalMenu({
          container,
          attributes: [
            "hierarchicalCategories.lvl0",
            "hierarchicalCategories.lvl1",
          ],
        }),
    ],
    fallbackWidget: ({ container, attribute }) =>
      panel({ templates: { header: attribute } })(menu)({ container, attribute }),
  });
  ```

  ```js Request customization theme={"system"}
  dynamicWidgets({
    container: "#dynamic-widgets",
    widgets: [
      (container) => refinementList({ container, attribute: "brand" }),
      (container) =>
        hierarchicalMenu({
          container,
          attributes: [
            "hierarchicalCategories.lvl0",
            "hierarchicalCategories.lvl1",
          ],
        }),
    ],
    fallbackWidget: ({ container, attribute }) =>
      panel({ templates: { header: attribute } })(menu)({ container, attribute }),
    // Send a second request after the widgets mount.
    facets: [],
    // Set this to at least the number of pinned values.
    maxValuesPerFacet: 100,
  });
  ```
</CodeGroup>

## Options

<ParamField body="container" type="string | HTMLElement" required>
  A CSS selector or `HTMLElement` that contains the widget.

  <CodeGroup>
    ```js String theme={"system"}
    dynamicWidgets({
      // ...
      container: "#dynamic-widgets",
    });
    ```

    ```js HTMLElement theme={"system"}
    dynamicWidgets({
      // ...
      container: document.querySelector("#dynamic-widgets"),
    });
    ```
  </CodeGroup>
</ParamField>

<ParamField body="widgets" type="Array<(container: HTMLElement) => Widget>" required>
  An array of functions that create refinement widgets.

  Each function receives a container and returns a widget.
  The returned widget must define an `attribute` or `attributes` option so that `dynamicWidgets` can match and order it.

  The function can also return a custom widget created with a connector.

  <CodeGroup>
    ```js Widget theme={"system"}
    dynamicWidgets({
      // ...
      widgets: [(container) => refinementList({ container, attribute: "brand" })],
    });
    ```

    ```js Hierarchical icon=code theme={"system"}
    dynamicWidgets({
      // ...
      widgets: [
        (container) =>
          hierarchicalMenu({
            container,
            attributes: [
              "hierarchicalCategories.lvl0",
              "hierarchicalCategories.lvl1",
            ],
          }),
      ],
    });
    ```

    ```js Panel theme={"system"}
    dynamicWidgets({
      // ...
      widgets: [
        (container) =>
          panel({
            templates: { header: "Brand" },
          })(refinementList)({
            container,
            attribute: "brand",
          }),
      ],
    });
    ```

    ```js Custom widget theme={"system"}
    const customRefinementList = connectRefinementList(() => {
      // Implement custom widget
    });

    dynamicWidgets({
      // ...
      widgets: [
        (container) =>
          customRefinementList({
            container,
            attribute: "brand",
          }),
      ],
    });
    ```
  </CodeGroup>
</ParamField>

<ParamField body="fallbackWidget" type="(args: { attribute: string, container: HTMLElement }) => Widget">
  A function that's called for every attribute you want to display.
  It receive a container and attribute and is expected to return a widget.
  The function can also return custom widgets created with connectors.

  <CodeGroup>
    ```js Widget theme={"system"}
    dynamicWidgets({
      // ...
      fallbackWidget: ({ container, attribute }) =>
        refinementList({ container, attribute }),
    });
    ```

    ```js Panel theme={"system"}
    dynamicWidgets({
      // ...
      fallbackWidget: ({ container, attribute }) =>
        panel({
          templates: { header: attribute },
        })(refinementList)({ container, attribute }),
    });
    ```

    ```js Custom widget theme={"system"}
    const customRefinementList = connectRefinementList(() => {
      // implement custom widget
    });

    dynamicWidgets({
      // ...
      fallbackWidget: ({ container, attribute }) =>
        customRefinementList({ container, attribute }),
    });
    ```
  </CodeGroup>
</ParamField>

<ParamField body="transformItems" type="function">
  Transforms the facet attributes before rendering.

  The second argument includes the search results, which you can use to derive a different attribute list.

  ```js JavaScript icon=code theme={"system"}
  dynamicWidgets({
    // ...
    transformItems(items, { results }) {
      return items;
    },
  });
  ```
</ParamField>

<ParamField body="facets" type="['*']|[]" default="['*']">
  The facets to apply before dynamic widgets are mounted.
  Setting the value to `['*']` requests all facets and avoids an additional network request.

  <CodeGroup>
    ```js Default theme={"system"}
    dynamicWidgets({
      // ...
      facets: ["*"],
    });
    ```

    ```js No facets theme={"system"}
    dynamicWidgets({
      // ...
      facets: [],
    });
    ```
  </CodeGroup>
</ParamField>

<ParamField body="maxValuesPerFacet" type="number" default={20}>
  The maximum number of values to request for each facet.

  Set this value to at least the highest `limit` or `showMoreLimit` used by a dynamic widget.
  This helps avoid an additional request after the widget mounts.

  Set `maxValuesPerFacet` to at least the largest number of pinned values configured for any facet.
  Otherwise, some pinned values might not appear.

  ```js JavaScript icon=code theme={"system"}
  dynamicWidgets({
    // ...
    maxValuesPerFacet: 500,
  });
  ```
</ParamField>

## Customize the UI with `connectDynamicWidgets`

Use `connectDynamicWidgets` to build a custom UI for `dynamicWidgets`.

Import the connector using the method that matches your InstantSearch.js installation.

<CodeGroup>
  ```js Package manager theme={"system"}
  import { connectDynamicWidgets } from "instantsearch.js/es/connectors";
  ```

  ```js CDN theme={"system"}
  const { connectDynamicWidgets } = instantsearch.connectors;
  // Use instantsearch.connectors.connectDynamicWidgets()
  ```
</CodeGroup>

Create a render function, pass it to `connectDynamicWidgets`,
and add the resulting widget to the search:

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

// 2. Create the custom widget
const customDynamicWidgets = connectDynamicWidgets(renderDynamicWidgets);

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

### Create a render function

InstantSearch.js calls the render function before the first search during the `init` lifecycle
and each time results come back from Algolia (`render` lifecycle step).

```js JavaScript icon=code theme={"system"}
const renderDynamicWidgets = (renderOptions, isFirstRender) => {
  const { attributesToRender, widgetParams } = renderOptions;

  if (isFirstRender) {
    // Set up the initial DOM and event listeners.
  }

  // Render the widget
};
```

#### Rendering options

<ParamField body="attributesToRender" type="string[]">
  The ordered facet attributes to render,
  derived from [`renderingContent.facetOrdering.facets.order`](/doc/api-reference/api-parameters/renderingContent#param-facets-order).

  ```js JavaScript icon=code theme={"system"}
  const renderDynamicWidgets = (renderOptions, isFirstRender) => {
    const { attributesToRender } = renderOptions;

    document.querySelector("#dynamic-widgets").innerHTML = `
      <ul>
        ${attributesToRender
          .map(
            (attribute) => `
              <li>
                ${attribute}
              </li>`,
          )
          .join("")}
      </ul>
    `;
  };
  ```
</ParamField>

<ParamField body="widgetParams" type="object">
  All original widget options forwarded to the render function.

  ```js JavaScript icon=code theme={"system"}
  const renderDynamicWidgets = (renderOptions, isFirstRender) => {
    const { widgetParams } = renderOptions;

    widgetParams.container.innerHTML = "...";
  };

  // ...

  search.addWidgets([
    customDynamicWidgets({
      // ...
      container: document.querySelector("#dynamic-widgets"),
    }),
  ]);
  ```
</ParamField>

### Create and instantiate the custom widget

First, create your custom widgets using a rendering function.
Then, instantiate them with parameters.

There are two kinds of parameters you can pass:

* **Instance parameters**. Predefined options that configure Algolia's behavior.
* **Custom parameters**. Parameters you define to make the widget reusable and adaptable.

Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`.

```js JavaScript icon=code theme={"system"}
const customDynamicWidgets = connectDynamicWidgets(
  renderDynamicWidgets
);

search.addWidgets([
  customDynamicWidgets({
    widgets: object[],
    // Optional parameters
    transformItems?: function,
    facets?: string[],
    maxValuesPerFacet?: number,
  })
]);
```

#### Instance options

<ParamField body="widgets" type="object[]" required>
  The widgets to add to the parent index.

  Update their DOM positions in the render function to match the order in `attributesToRender`.

  ```js JavaScript icon=code theme={"system"}
  customDynamicWidgets({
    widgets: [
      refinementList({
        // ...
      }),
      customWidget({
        // ...
      }),
    ],
  });
  ```
</ParamField>

<ParamField body="transformItems" type="function">
  A function to transform the attributes to render,
  or using a different source to determine the attributes to render.

  ```js JavaScript icon=code theme={"system"}
  customDynamicWidgets({
    // ...
    transformItems(items, { results }) {
      return items;
    },
  });
  ```
</ParamField>

<ParamField body="facets" type="['*']|[]" default="['*']">
  The facets to apply before dynamic widgets get mounted.
  Setting the value to `['*']` will request all facets and avoid an additional network request once the widgets are added.

  <CodeGroup>
    ```js Default theme={"system"}
    customDynamicWidgets({
      // ...
      facets: ["*"],
    });
    ```

    ```js No facets theme={"system"}
    customDynamicWidgets({
      // ...
      facets: [],
    });
    ```
  </CodeGroup>
</ParamField>

<ParamField body="maxValuesPerFacet" type="number" default={20}>
  The default number of facet values to request.
  It's recommended to have this value at least as high as the highest limit and showMoreLimit of dynamic widgets,
  as this will prevent a second network request once that widget mounts.

  To avoid pinned items not showing in the result,
  make sure you choose a `maxValuesPerFacet` at least as high as all the most pinned items you have.

  ```js JavaScript icon=code theme={"system"}
  customDynamicWidgets({
    // ...
    maxValuesPerFacet: 500,
  });
  ```
</ParamField>

### Full example

```html HTML icon=code-xml theme={"system"}
<div id="dynamic-widgets"></div>
```

```js JavaScript icon=code expandable theme={"system"}
// Create a mapping between widgets and their attribute
const { rangeSlider, refinementList } = instantsearch.widgets;
const widgets = {
  brand: {
    widget: (container) => refinementList({ attribute: "brand", container }),
    container: document.createElement("div"),
  },
  price: {
    widget: (container) => rangeSlider({ attribute: "price", container }),
    container: document.createElement("div"),
  },
};

// Create a render function
const renderDynamicWidgets = (renderOptions, isFirstRender) => {
  const { attributesToRender, widgetParams } = renderOptions;

  if (isFirstRender) {
    const ul = document.createElement("ul");
    widgetParams.container.appendChild(ul);
  }

  const ul = widgetParams.container.querySelector("ul");
  ul.innerHTML = "";

  attributesToRender.forEach((attribute) => {
    if (!widgets[attribute]) {
      return;
    }

    const container = widgets[attribute].container;
    ul.appendChild(container);
  });
};

// Create the custom widget
const customDynamicWidgets = connectDynamicWidgets(renderDynamicWidgets);

// Instantiate
search.addWidgets([
  customDynamicWidgets({
    container: document.querySelector("#dynamic-widgets"),
    widgets: Object.values(widgets).map(({ widget, container }) =>
      widget(container),
    ),
  }),
]);
```
