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

# Chat

> Displays a chat interface to interact with a generative AI assistant built with Algolia Agent Studio.

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="react"
    baseHref="/doc/api-reference/widgets/chat"
    options={[
{ value: "js", label: "JavaScript", description: "InstantSearch.js" },
{ value: "react", label: "React", description: "React InstantSearch" },
]}
  />
</div>

For more information, see [Agent Studio](/doc/guides/algolia-ai/agent-studio).

```tsx Signature theme={"system"}
<Chat
  // Required prop, either one of
  agentId={string}
  transport={object}
  // Optional props (agentId only)
  feedback={boolean}
  requestOptions={object}
  // Optional props
  getSearchPageURL={function}
  tools={object}
  context={object | function}
  initialMessages={array}
  initialUserMessage={string}
  resume={boolean}
  persistence={boolean | object}
  disableTriggerValidation={boolean}
  onFinish={function}
  sendAutomaticallyWhen={function}
  requiresSearch={boolean}
  showReasoning={boolean}
  loaderPosition={string}
  shouldShowLoader={function}
  loaderShowDelay={number}
  loaderMinDuration={number}
  messagesProps={object}
  classNames={object}
  translations={object}
  layoutComponent={function}
  // Optional component props
  reasoningComponent={function}
  itemComponent={function}
  headerCloseIconComponent={function}
  headerMaximizeIconComponent={function}
  headerMinimizeIconComponent={function}
  headerTitleIconComponent={function}
  messagesErrorComponent={function}
  loaderComponent={function}
  promptFooterComponent={function}
  promptHeaderComponent={function}
  suggestionsComponent={function}

  ...props={ComponentProps<'div'>}
/>
```

## Import

```jsx JavaScript icon=code theme={"system"}
import { Chat } from "react-instantsearch";
```

<Card title="See this widget in action" icon="monitor-play" href="https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference&flavor=react&widget=chat" horizontal>
  Preview this widget and its behavior.
</Card>

## About this widget

`Chat` is a widget to display a chat interface that interacts with a generative AI assistant.

See also: [Agent Studio](/doc/guides/algolia-ai/agent-studio)

<Note>
  `Chat` renders the chat panel but doesn't provide a way to open it.
  Add one entry point to the same `InstantSearch` instance: the [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react) widget, the [`PromptSuggestions`](/doc/api-reference/widgets/prompt-suggestions/react) widget, [AI mode](/doc/guides/building-search-ui/going-further/chat-customization/ai-search-experience/react) on a `SearchBox` or autocomplete, or an [inline layout](#param-layout-component).
  Otherwise, the widget logs a development warning.
  To silence it, set [`disableTriggerValidation`](#param-disable-trigger-validation) to `true`.
</Note>

## Examples

```jsx JavaScript icon=code theme={"system"}
import React from "react";
import { liteClient as algoliasearch } from "algoliasearch/lite";
import { InstantSearch, Chat, ChatTrigger } from "react-instantsearch";

const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey");

function App() {
  return (
    <InstantSearch indexName="instant_search" searchClient={searchClient}>
      <Chat agentId="8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9" />
      <ChatTrigger />
    </InstantSearch>
  );
}
```

## Props

<ParamField body="agentId" type="string">
  The unique identifier of the agent to connect to.
  You can find the `agentId` in the [Agent Studio dashboard](https://dashboard.algolia.com/generativeAi/agent-studio/agents).

  ```jsx JavaScript icon=code theme={"system"}
  <Chat agentId="8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9" />
  ```
</ParamField>

<ParamField body="feedback" type="boolean">
  Whether to enable feedback (thumbs up/down) on assistant messages.
  Only available when using `agentId`.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    agentId="8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9"
    feedback={true}
  />
  ```
</ParamField>

<ParamField body="requestOptions" type="object">
  Request options to send with the built-in Agent Studio completion requests.
  Use this option only with `agentId`.
  It isn't available with a custom `transport` or custom `chat` instance.

  The object accepts the following properties:

  * `queryParameters` (`Record<string, string | number | boolean>`). Query parameters to append to each completion request.
  * `headers` (`Record<string, string> | Headers`). Headers to send with each completion request.

  InstantSearch always keeps `compatibilityMode=ai-sdk-5`,
  even if you set `compatibilityMode` in `queryParameters`.
  Custom headers can't override the required Algolia identity headers (`x-algolia-application-id`, `x-algolia-api-key`) or the `x-algolia-agent` header that identifies InstantSearch.
  When users regenerate an assistant message,
  InstantSearch sends `cache=false` even if `requestOptions.queryParameters.cache` is `true`.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    agentId="8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9"
    requestOptions={{
      queryParameters: {
        cache: false,
      },
      headers: {
        'X-Algolia-Secure-User-Token': 'ALGOLIA_SECURE_USER_TOKEN',
      },
    }}
  />
  ```
</ParamField>

<ParamField body="transport" type="HttpChatTransportInitOptions">
  A custom transport object to handle the communication between the chat widget and the agent.
  The API endpoint must be compatible with [Vercel AI SDK 5](https://ai-sdk.dev/docs/ai-sdk-ui/transport#custom-transport-configuration).

  <CodeGroup>
    ```jsx JavaScript icon=code theme={"system"}
    <Chat transport={{
      api: 'https://chatapi.example.com/api/v1/chat',
      headers: {
        'X-Session-Id': '8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9',
        'X-Api-Version': '2025-01-01',
      },
    }} />;
    ```

    ```ts HttpChatTransportInitOptions icon="file-type" theme={"system"}
    type HttpChatTransportInitOptions = {
      api?: string;
      credentials?: Resolvable<RequestCredentials>;
      headers?: Resolvable<Record<string, string> | Headers>;
      body?: Resolvable<object>;
      fetch?: FetchFunction;
      // ...
    };
    ```
  </CodeGroup>
</ParamField>

<ParamField body="getSearchPageURL" type="(nextUiState: object) => string">
  A function to return the URL of the main search page with the `nextUiState`.
  This is used to go to the main search page when the user clicks on "View all" in the search tool.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    getSearchPageURL={(nextUiState) => {
      return `/search?${qs.stringify(nextUiState)}`;
    }}
  />
  ```
</ParamField>

<ParamField body="tools" type="Record<string, Tool>">
  An object defining the client-side tools that the agent can use to interact with your application.
  The object's keys are tool names that must match the tools defined in the [Agent Studio dashboard](/doc/guides/algolia-ai/agent-studio/how-to/dashboard#client-side-tools).

  The widget has built-in renderers for the search and grouped results tools. Import and use the exported constants as keys to customize the default rendering:

  * `SearchIndexToolType` (`'algolia_search_index'`). Displays search results from an Algolia index in a carousel.
  * `GroupedResultsToolType` (`'algolia_grouped_results'`). Displays grouped results from the tool's streaming input in carousels.

  The widget matches an override against the exact tool name.
  If your agent can still send the deprecated name, register your override under both constants.

  ```jsx JavaScript icon=code theme={"system"}
  import {
    GroupedResultsToolType,
    SearchIndexToolType,
  } from 'react-instantsearch';
  ```

  To customize only the appearance of each result in the built-in carousel, use the [`itemComponent`](#param-item-component) prop instead.
  To replace the whole carousel layout, override the `SearchIndexToolType` tool's `layoutComponent`.
  The tool result is on `context.message.output` (`hits`, `nbHits`, `queryID`):

  ```jsx JavaScript icon=code expandable theme={"system"}
  <Chat
    agentId="YOUR_AGENT_ID"
    tools={{
      [SearchIndexToolType]: {
        layoutComponent: ({ context }) => {
          const items = context.message.output?.hits || [];

          return (
            <div className="MyCarousel">
              {items.map((item) => (
                <article className="MyCarousel-item" key={item.objectID}>
                  <img src={item.image} alt={item.name} />
                  <h3>{item.name}</h3>
                  <span>${item.price}</span>
                </article>
              ))}
            </div>
          );
        },
      },
    }}
  />
  ```

  For a step-by-step walkthrough, see [Customize the chat results carousel](/doc/guides/building-search-ui/going-further/chat-customization/results-carousel/react).

  Each tool is an object with the following properties:

  * `layoutComponent`. A React component that renders the tool call in the chat. It receives a single `context` prop holding the tool's own data alongside the shared chat state:

    * `context.message`. The tool call message. It contains `input` (parameters from the agent) and `output` (the result you provide with `addToolResult`). For more information about the message structure, see the [Vercel AI SDK documentation](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#messages.ui-message).
    * `context.records`. The records the chat's tools have fetched during the conversation, keyed by `objectID`. A tool that receives only object IDs reads the full record with `context.records.get(objectID)` rather than searching for it again. It also provides `has(objectID)` and `getAll()`.
    * `context.indexUiState`. The current InstantSearch UI state.
    * `context.setIndexUiState`. Updates the InstantSearch UI state (for example, to refine filters or update the query based on the tool call).
    * `context.applyFilters`. Applies filters to the InstantSearch UI state from the tool call.
    * `context.addToolResult`. Sends the tool's output back to the agent. Call this at least once before the next message. For more information, see the [Vercel AI SDK documentation](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#add-tool-result).
    * `context.onClose`. Dismisses the tool's UI in the chat.
    * `context.sendEvent`. Sends `click` or `conversion` events related to the tool call. For more information, see the [`insights`](/doc/api-reference/widgets/insights/react) middleware documentation.
    * `context` also carries the shared chat state available to `actionsComponent`, [`emptyComponent`](#param-empty-component), [`loaderComponent`](#param-loader-component), and [`messagesErrorComponent`](#param-components-props), such as `messages`, `status`, and `stop`.

    The root-level props, such as `message` and `addToolResult`, are deprecated.
    The widget also passes them alongside `context` so components written for earlier API versions keep working.
    Read these props from `context` instead.
  * `cancelOutput`. Optional function that returns the output to report when the chat starts a new message while the tool call is waiting for a result. For example, a confirmation prompt can return `{ confirmed: false }`. The function receives `{ toolCallId, input }`. If you don't provide it, the chat reports the tool call as failed. This only changes the output reported to the agent. The tool keeps waiting locally, so you can still submit a result until the [`timeout`](#param-tools) settles it. An interactive tool, such as a confirmation prompt that waits for users, needs `timeout: false` to stay open.
  * `onToolCall`. Optional handler invoked when the agent calls the tool. Receives a parameter object with:

    * `input`. The parameters the agent passed to the tool.
    * `addToolResult`. Sends the tool's output back to the agent. For more information, see the [Vercel AI SDK documentation](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#on-tool-call). Call it with `{ output }` to report a result, or with `{ state: 'output-error', errorText }` to report a failure the agent can read and explain.
    * `signal`. An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that aborts when the tool call stops mattering: the `timeout` elapsed, or the response was stopped. Pass it to your own `fetch` calls so abandoned work doesn't keep running.
    * `toolCallId`. The unique identifier of the tool call.
    * `toolName`. The name of the tool being invoked.
    * `dynamic`. Whether the tool is dynamically registered.

    `onToolCall` can be `async`. When it throws or rejects, the chat settles the tool call as failed with the error's message, so the turn finishes and the agent can respond to the failure instead of hanging.
  * `timeout`. Optional number of milliseconds for `onToolCall` to submit a result, `20000` by default. When the deadline passes, the chat aborts the handler's `signal` and settles the tool call as failed. Set it to `false` to wait indefinitely, which is what an interactive tool needs, such as a confirmation prompt that waits for users.
  * `retryOnError`. Optional boolean, `false` by default. When `true`, the default failed state shows a **Retry** button next to the error message. Clicking it regenerates the assistant response, so the agent can call the tool again. Turn it on only when repeating the operation is safe, because the first attempt may have completed its write before failing. The button only appears when the tool has an `onToolCall` handler, the chat is idle, and the shopper hasn't sent another message since. With a `layoutComponent`, the default state doesn't render, so handle recovery yourself with `context.onReload`. To change the button text, use the [`translations.message.toolErrorRetryText`](#param-translations) translation.
  * `streamInput`. Optional boolean. When `true`, the default loader is suppressed as the tool's input is streamed from the agent. Use the partial `input` in your `layout` template to render the streaming state as input chunks arrive.
  * `shouldRender`. Optional function that decides whether this tool call renders at all. It receives the chat context, plus `message` (the tool call under consideration) and `parentMessage` (the chat message it belongs to). Returning `false` skips the tool call and keeps the loader visible, so a tool can step aside for another one that renders in the same turn. When you omit it, the tool always renders. Overriding a built-in tool's `layoutComponent` keeps that tool's own `shouldRender` unless you pass your own.
  * `matchesToolName`. Optional function that claims the tool names your agent's server derives from this tool. It receives the incoming tool name and returns `true` when this tool handles it, for example `matchesToolName: (toolName) => toolName.startsWith('my_tool_')`. `Chat` first looks for a tool registered under the exact name and only then asks the other tools whether they claim it, so a claim never shadows another registration. When you omit it, the tool only handles calls sent under its own key. The built-in `SearchIndexToolType` tool already claims the index-suffixed names the Algolia MCP Server exposes, such as `algolia_search_index_products`.

  A failed tool call renders its error text in the conversation, in a `.ais-ChatMessage-toolError` element, even for a tool with no `layoutComponent`. With a `layoutComponent`, your own component renders the failed state, so read `context.message.state` and `context.message.errorText` to handle it.

  With [`persistence`](#param-persistence), a reload can interrupt a tool call before its result arrives. On the next page load, `Chat` settles the tool calls that were still pending as failed, so a restored conversation doesn't wait forever on a handler that no longer exists. It doesn't run `onToolCall` again, because the first attempt may have completed its write. Tool calls with `timeout: false`, and tool calls for tools with no `onToolCall` handler, keep waiting instead: those are the ones users can still answer. With [`resume`](#param-resume), `Chat` waits for the resume attempt to finish first, so a stream that's still running settles its own tool calls.

  <CodeGroup>
    ```jsx JavaScript icon=code expandable theme={"system"}
    <Chat tools={{
      addToCart: {
        layoutComponent: ({ context }) => <div>
          <p>Add {context.message.input.objectID} to the cart?</p>
          <button onClick={async () => {
            // add the product to the cart
            await addProductToCart(context.message.input.objectID);
            // notify the agent that the tool has been used
            context.addToolResult({
              output: {
                text: `added ${context.message.input.objectID} to cart`,
                done: true,
              },
            });
          }}>Add to cart</button>
        </div>,
        // if the shopper sends a new message before answering, tell the agent
        // the item wasn't added rather than reporting a failure
        cancelOutput: () => ({ text: "not added to cart", done: false }),
        onToolCall: ({ addToolResult }) => addToolResult({ output: {} }),
      },
      viewProduct: {
        layoutComponent: ({ context }) => {
          if (!context.message.output) {
            return <span>Loading product...</span>;
          }

          return <div>
            <h2>{context.message.output.productName}</h2>
            <p>{context.message.output.brand}</p>
            <img src={context.message.output.imageUrl} />
          </div>;
        },
        // fetching details is safe to repeat, so offer a Retry button on failure
        retryOnError: true,
        // give the lookup 5 seconds instead of the default 20
        timeout: 5000,
        onToolCall: async ({ input, addToolResult, signal }) => {
          addToolResult({
            // fetch product details from your index
            output: await fetchProductDetails(input.objectID, { signal }),
          });
        },
      },

    }} />;

    ```

    ```ts Tool icon="file-type" expandable theme={"system"}
    type Tool = {
      layoutComponent?: (props: ToolLayoutComponentProps) => JSX.Element;
      cancelOutput?: (params: { toolCallId: string; input: unknown }) => unknown;
      onToolCall?: (params: {
        input: unknown;
        addToolResult: (
          props:
            | { state?: "output-available"; output: unknown }
            | { state: "output-error"; errorText: string }
        ) => Promise<void>;
        signal: AbortSignal;
        toolCallId: string;
        toolName: string;
        dynamic?: boolean;
      }) => void | PromiseLike<void>;
      timeout?: number | false;
      retryOnError?: boolean;
      streamInput?: boolean;
      shouldRender?: (context: ClientSideToolShouldRenderContext) => boolean;
      matchesToolName?: (toolName: string) => boolean;
    };

    type ToolLayoutComponentProps = {
      context: ChatComponentContext & {
        message: ChatToolMessage;
        records?: ChatRecordsStore;
        insightsEventContext?: ChatInsightsEventContext;
        indexUiState: object;
        setIndexUiState: (state: object) => void;
        addToolResult: AddToolResultWithOutput;
        applyFilters: (params: ApplyFiltersParams) => SearchParameters;
        sendEvent: SendEventForHits;
      };
    };

    type ApplyFiltersParams = {
      query?: string;
      facetFilters?: string[][];
      numericFilters?: string[];
    };
    ```
  </CodeGroup>
</ParamField>

<ParamField body="context" type="Record<string, string> | () => Record<string, string>">
  Extra context to send with each user message (for example, the current page or selected locale).
  The widget sends this context with every message, but doesn't show it in the chat UI.

  `context` can be a static object or a function that returns an object at send time.
  The widget attaches it to the latest user message as `metadata.turnContext`, following the Agent Studio [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context) contract.
  The context never appears as a chat bubble.

  The context must be a flat object that maps strings to strings (up to 32 keys, 4,096 bytes total).
  Agent Studio validates the payload and rejects malformed contexts.
  For details, see [Per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context).

  <Warning>
    The widget sends `context` to the agent in plain text.
    Don't put secrets, access tokens, or personally identifiable information you don't intend to share with the model in this field.
  </Warning>

  <CodeGroup>
    ```jsx Static object theme={"system"}
    <Chat
      agentId="YOUR_AGENT_ID"
      context={{ locale: "en", currentPage: "/products" }}
    />
    ```

    ```jsx Function theme={"system"}
    <Chat
      agentId="YOUR_AGENT_ID"
      context={() => ({
        locale: navigator.language,
        currentPage: window.location.pathname,
      })}
    />
    ```
  </CodeGroup>
</ParamField>

<ParamField body="initialMessages" type="UIMessage[]">
  Messages to pre-populate the chat with when it's initialized.
  These messages are added without triggering an AI response.

  `initialMessages` only applies when the chat doesn't have any existing messages.
  When `resume` is enabled, `initialMessages` is ignored.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    agentId="YOUR_AGENT_ID"
    initialMessages={[
      {
        id: "welcome",
        role: "assistant",
        parts: [{ type: "text", text: "Hi! How can I help you today?" }],
      },
    ]}
  />
  ```
</ParamField>

<ParamField body="initialUserMessage" type="string">
  A message to send automatically when the chat is initialized.

  `initialUserMessage` is only sent when the chat doesn't have any existing messages.
  It's sent after `initialMessages` are applied.
  When `resume` is enabled, this message isn't sent.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    agentId="YOUR_AGENT_ID"
    initialUserMessage="Show me a few popular products to get started."
  />
  ```
</ParamField>

<ParamField body="resume" type="boolean" default={false}>
  Whether to resume an ongoing chat generation stream when the widget mounts.
  Use this when restoring a chat session after a page reload to continue receiving an in-flight assistant response.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat agentId="YOUR_AGENT_ID" resume />
  ```
</ParamField>

<ParamField body="persistence" type="boolean | { messages?: boolean; open?: boolean }" default={true}>
  Whether to persist and restore the conversation and the open state of the chat panel from `sessionStorage`.
  When `true` (the default), the widget saves both the messages and whether the panel is open, and restores them when the widget re-mounts (for example, after a page reload).
  Set it to `false` to keep everything in memory only, so it's cleared when the page reloads.

  To control each policy separately, pass an object with the `messages` and `open` booleans. Only what you set to `true` is persisted:

  * Omitted or `true`: persists the messages and the open state.
  * `false` or `{}`: persists nothing.
  * `{ messages: true }`: persists the messages, but always starts with the panel closed.
  * `{ open: true }`: persists the open state, but doesn't persist messages.

  With a custom `chat` instance, the instance owns message persistence, so only the `open` option is available.
  The open state is persisted by default: pass `{ open: false }` to opt out.

  <Note>
    Changing the props of `Chat` rebuilds its internal chat instance, which drops the open state and any messages that persistence doesn't cover.
    Keep prop references stable, especially functions, or pass your own `chat` instance to keep the conversation.
    In development, the widget warns when a prop change is about to discard state.
  </Note>

  <CodeGroup>
    ```jsx Disable persistence theme={"system"}
    <Chat agentId="YOUR_AGENT_ID" persistence={false} />
    ```

    ```jsx Persist only the open state theme={"system"}
    <Chat agentId="YOUR_AGENT_ID" persistence={{ open: true }} />
    ```

    ```jsx Custom chat instance theme={"system"}
    <Chat chat={chatInstance} persistence={{ open: false }} />
    ```
  </CodeGroup>
</ParamField>

<ParamField body="disableTriggerValidation" type="boolean" default={false}>
  Whether to skip the validation that requires a way to open the chat.
  By default, `Chat` logs a development warning unless the chat has an entry point: a [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react) widget, a [`PromptSuggestions`](/doc/api-reference/widgets/prompt-suggestions/react) widget, [AI mode](/doc/guides/building-search-ui/going-further/chat-customization/ai-search-experience/react) on a `SearchBox` or autocomplete, or an [inline layout](#param-layout-component).
  Set this to `true` when you open the chat programmatically and don't render any of those.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat agentId="YOUR_AGENT_ID" disableTriggerValidation />
  ```
</ParamField>

<ParamField body="onFinish" type="(options: { message, messages, isAbort, isDisconnect, isError }) => void">
  A callback called when the assistant response has finished streaming, including when the stream is aborted, disconnected, or fails.

  The callback receives an object with:

  * `message`: the final assistant message.
  * `messages`: the full message list including the new message.
  * `isAbort`: `true` if the stream was stopped with `stop()`.
  * `isDisconnect`: `true` if the connection was lost.
  * `isError`: `true` if the stream finished with an error.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    agentId="YOUR_AGENT_ID"
    onFinish={({ message, isAbort, isError }) => {
      if (isError) {
        console.error("Chat stream failed", message);
        return;
      }
      if (!isAbort) {
        analytics.track("chat_message_completed", { messageId: message.id });
      }
    }}
  />
  ```
</ParamField>

<ParamField body="sendAutomaticallyWhen" type="(options: { messages: UIMessage[] }) => boolean | Promise<boolean>">
  A function that decides whether to resubmit the conversation to the agent without user input.
  It receives an object with `messages`, the current message list, and returns a boolean or a promise that resolves to one.
  When it returns `true`, `Chat` sends the conversation again so the agent can continue its turn.

  By default, `Chat` uses `lastAssistantMessageIsCompleteWithToolCalls`.
  It returns `true` when the last step of the last assistant message has at least one client-side tool call, and every one of those calls has an output or an error.
  This is what lets the agent act on the results of your [`tools`](#param-tools) and keep answering.
  The function ignores tool calls the agent executes on its own side, because their results don't come from your app.

  `Chat` evaluates the function only for a response with at least one required client-side tool call.
  It waits for the response to finish streaming and for every required client-side tool call in that response to have a result, whichever comes last.
  It evaluates each response at most once, and skips a stream that's stopped or fails.

  Return `false` to stop `Chat` from continuing turns on its own, for example when your tools end the turn and users send the next message.

  With a custom `chat` instance, set `sendAutomaticallyWhen` on the instance instead.

  <CodeGroup>
    ```jsx Never continue automatically theme={"system"}
    <Chat agentId="YOUR_AGENT_ID" sendAutomaticallyWhen={() => false} />
    ```

    ```jsx Continue for one tool only expandable theme={"system"}
    import { lastAssistantMessageIsCompleteWithToolCalls } from "instantsearch.js/es/lib/ai-lite";

    <Chat
      agentId="YOUR_AGENT_ID"
      sendAutomaticallyWhen={({ messages }) => {
        if (!lastAssistantMessageIsCompleteWithToolCalls({ messages })) {
          return false;
        }

        const lastMessage = messages[messages.length - 1];
        const lastStepStartIndex = lastMessage.parts.reduce(
          (lastIndex, part, index) =>
            part.type === "step-start" ? index : lastIndex,
          -1
        );

        return lastMessage.parts.slice(lastStepStartIndex + 1).some(
          (part) =>
            part.type === "tool-applyFilters" && !part.providerExecuted
        );
      }}
    />;
    ```
  </CodeGroup>
</ParamField>

<ParamField body="requiresSearch" type="boolean" default={true}>
  Whether the widget makes InstantSearch require a main search request.
  By default (`true`), the widget contributes to the main search.
  Set it to `false` to opt the widget out of the main search: if it's the only widget on the page, no main search request is sent.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat agentId="YOUR_AGENT_ID" requiresSearch={false} />
  ```
</ParamField>

<ParamField body="showReasoning" type="boolean" default={true}>
  Whether to display the reasoning an agent sends.
  Each assistant message shows its reasoning as a single collapsed disclosure.
  Expanding it lists the reasoning steps in the order they arrived, and while reasoning is in progress the collapsed row shows the latest step.
  The reader's expanded or collapsed choice persists for the rest of the turn.

  Set it to `false` to hide reasoning in this widget.
  It also hides a custom [`reasoningComponent`](#param-reasoning-component).

  This prop can't make an agent send reasoning.
  Whether reasoning reaches the browser depends on the agent's own [`sendReasoning`](/doc/guides/algolia-ai/agent-studio/how-to/agent-configuration) setting, which is off by default.

  Copied assistant messages include the answer text, not the reasoning.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat agentId="YOUR_AGENT_ID" showReasoning={false} />
  ```
</ParamField>

<ParamField body="loaderPosition" type="string" default="messages-end">
  Where the loader appears while a chat turn is in progress.

  * `messages-end`. Renders the loader in its own row after the last message.
  * `message-inline`. Renders the loader inside the streaming assistant message, after the content received so far. Before the first assistant part arrives, the loader appears in its own row.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat agentId="YOUR_AGENT_ID" loaderPosition="message-inline" />
  ```

  To place the loader elsewhere in the chat panel, use CSS.
  See [Control when and where the chat loader shows](/doc/guides/building-search-ui/going-further/chat-customization/loader-behavior/react).
</ParamField>

<ParamField body="shouldShowLoader" type="(context) => boolean">
  Controls when the loader appears.
  The function receives the loader context and `defaultValue`,
  which indicates whether the widget would show the loader by default.
  The [timing options](#param-loader-show-delay) still apply to the returned value.

  Relevant context properties include:

  * `status`. The current chat status: `'ready'`, `'submitted'`, `'streaming'`, or `'error'`.
  * `phase`. The current phase of the turn: `'submitted'`, `'tool'`, `'reasoning'`, or `'thinking'`.
  * `message`. The assistant message associated with the loader, when available.
  * `messages`. The full conversation.
  * `tools`. The tools available to the agent.
  * `defaultValue`. Whether the widget would show the loader by default.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    agentId="YOUR_AGENT_ID"
    shouldShowLoader={({ phase, defaultValue }) => phase === "tool" || defaultValue}
  />
  ```
</ParamField>

<ParamField body="loaderShowDelay" type="number" default={250}>
  The delay, in milliseconds, before the loader reappears after being hidden earlier in the same turn.
  The first loader in a turn isn't delayed.
  Set this option to `0` to show the loader immediately after loading starts.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat agentId="YOUR_AGENT_ID" loaderShowDelay={400} />
  ```
</ParamField>

<ParamField body="loaderMinDuration" type="number" default={200}>
  The minimum time, in milliseconds, that the loader remains visible while the turn is running.
  This prevents the loader from briefly appearing and disappearing.
  The loader hides immediately after the turn ends.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat agentId="YOUR_AGENT_ID" loaderMinDuration={300} />
  ```
</ParamField>

<ParamField body="emptyComponent" type="(props: ChatComponentPropsWithContext<ChatEmptyProps>) => JSX.Element">
  A component to customize the welcome screen shown before the first message, when the chat doesn't have any messages yet.
  Use it to display a greeting and starter prompts that send a message when clicked.
  It receives `context`, the supported API for shared chat state and callbacks:

  * `context.sendMessage`. Sends a message to the agent. Call it with `{ text }` to submit a starter prompt as the first message.
  * `context.status`. The current chat status.
  * `context.onClose`. Dismisses the chat.
  * `context.setInput`. Sets the value of the prompt input without sending it.

  The deprecated root-level `sendMessage`, `status`, `onClose`, and `setInput` props are also passed for compatibility.
  Read these values from `context`.

  ```jsx JavaScript icon=code expandable theme={"system"}
  import { Chat } from "react-instantsearch";

  function WelcomeScreen({ context }) {
    const prompts = ["What are your best-selling shoes?", "Show me new arrivals"];

    return (
      <div className="ais-ChatGreeting">
        <h2>How can I help you today?</h2>
        <div className="ais-ChatPromptSuggestions">
          {prompts.map((prompt) => (
            <button key={prompt} onClick={() => context.sendMessage({ text: prompt })}>
              {prompt}
            </button>
          ))}
        </div>
      </div>
    );
  }

  <Chat agentId="YOUR_CHAT_AGENT_ID" emptyComponent={WelcomeScreen} />;
  ```

  To render the default greeting (heading, subheading, and an optional banner) inside your component, use the exported `ChatGreeting` component from `react-instantsearch`.

  For a step-by-step walkthrough, see [Show starter prompts on the chat welcome screen](/doc/guides/building-search-ui/going-further/chat-customization/welcome-screen/react).
</ParamField>

<ParamField body="layoutComponent" type="(props: ChatLayoutOwnProps) => JSX.Element">
  A component to customize the overall layout of the chat widget.
  Use `ChatInlineLayout` for an inline (non-overlay) layout, `ChatSidePanelLayout` for a side panel on the right side of the page, or `ChatOverlayLayout` for the default floating overlay.
  To display a custom welcome screen before the first message,
  see [Show starter prompts on the chat welcome screen](/doc/guides/building-search-ui/going-further/chat-customization/welcome-screen/react).

  Use the side panel layout to keep the chat widget beside your page content on wider screens.
  Unlike the inline layout, the side panel requires an entry point to open the widget.

  By default, `ChatSidePanelLayout` adjusts `document.body`.
  Set its `parentElement` prop to a CSS selector to adjust another element.

  * When the side panel opens, the layout adds the value of `--ais-chat-width` to the selected element's inline `margin-right`.
  * When the side panel closes, the layout restores the previous inline margin.
  * On wider screens, maximizing the chat widget changes the panel width to `--ais-chat-maximized-width` without increasing the reserved margin, so the maximized panel can overlap page content.

  At viewport widths of 680px or less, the side panel fills the viewport.
  This breakpoint is based on the viewport width, not the width of `parentElement`.

  ```jsx JavaScript icon=code theme={"system"}
  import { Chat, ChatSidePanelLayout } from "react-instantsearch";

  function SidePanelLayout(props) {
    return (
      <ChatSidePanelLayout
        {...props}
        parentElement="#main-content"
      />
    );
  }

  <Chat
    agentId="8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9"
    layoutComponent={SidePanelLayout}
  />
  ```
</ParamField>

<ParamField body="loaderComponent" type="(props) => JSX.Element">
  A component to customize the loader shown while waiting for an AI response.
  Import the built-in `ChatMessageLoader` component from `react-instantsearch` to render the default loader, or provide your own component.

  The component receives:

  * `translations`. The resolved loader translations.
  * `context`. The same context as [`shouldShowLoader`](#param-should-show-loader), without `defaultValue`.
  * `inline`. Whether the loader renders inside an assistant message instead of in its own row. It's `true` when `loaderPosition` is `message-inline` and an assistant message is available.

  ```jsx JavaScript icon=code theme={"system"}
  import { Chat, ChatMessageLoader } from "react-instantsearch";

  <Chat
    agentId="8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9"
    loaderComponent={(props) => (
      <ChatMessageLoader
        {...props}
        translations={{ loaderText: "Thinking..." }}
      />
    )}
  />
  ```

  To replace the loading state, see [Customize the chat loader](/doc/guides/building-search-ui/going-further/chat-customization/loading-message/react).
</ParamField>

<ParamField body="reasoningComponent" type="(props: ChatMessageReasoningComponentProps) => JSX.Element">
  A component that renders the reasoning of an assistant message.
  It replaces the built-in disclosure.
  Unlike the built-in disclosure, which groups the whole message into one row, this component renders once per reasoning part, at that part's own position in the stream.

  The component receives:

  * `part`. The reasoning part to render. Its `text` property holds the reasoning text.
  * `partIndex`. The index of the reasoning part in the message's `parts` array.
  * `isStreaming`. Whether this reasoning part is still being produced.
  * `message`. The message the reasoning part belongs to.
  * `context`. The shared chat state, such as `messages`, `status`, and `tools`.

  The component changes how reasoning renders, not whether it renders: [`showReasoning={false}`](#param-show-reasoning) hides this component along with the built-in disclosure.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    agentId="YOUR_AGENT_ID"
    reasoningComponent={({ part, isStreaming }) => (
      <p className="MyReasoning" data-streaming={isStreaming}>
        {part.text}
      </p>
    )}
  />
  ```
</ParamField>

<ParamField body="components props" type="() => JSX.Element">
  Components to customize the rendering of the widget.

  `actionsComponent`, [`emptyComponent`](#param-empty-component), [`loaderComponent`](#param-loader-component), and `messagesErrorComponent` receive a `context` prop that groups the current chat state and callbacks.
  The other components use the props described below.

  * `context.messages`, `context.status`, `context.error`, `context.activePart`. The conversation and where it stands. For the `status` values, see [Status values](#status-values).
  * `context.open`, `context.maximized`, `context.isClearing`. Panel state, for conditional rendering.
  * `context.tools`. The tools registered for the assistant.
  * `context.sendMessage`, `context.regenerate`, `context.stop`, `context.setInput`. Drive the conversation from a custom control.
  * `context.onReload`, `context.onNewConversation`, `context.onClose`. Regenerate a message, clear the conversation, or close the panel.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    messagesErrorComponent={({ context, errorMessage }) => (
      <p data-chat-status={context.status}>{errorMessage}</p>
    )}
  />
  ```

  * `itemComponent`. Custom component for each result. Receives an object containing a single record.
  * `headerCloseIconComponent`. Header: close icon component.
  * `headerMaximizeIconComponent`. Header: maximize icon component. Receives a prop containing `{ maximized: boolean }` for conditional rendering.
  * `headerMinimizeIconComponent`. Header: minimize icon component.
  * `headerTitleIconComponent`. Header: title icon component (defaults to sparkles).
  * `messagesErrorComponent`. Messages: custom error component. Guardrail fallback responses render as assistant messages, not through this component. By default, the widget shows a generic message (`Sorry, we are not able to generate a response at the moment. Please contact support.`) and a "Start a new conversation" button that clears the conversation. The component receives:
    * `errorMessage`. The raw error message from the API or transport layer.
    * `onNewConversation`. Callback that clears the conversation and starts a new one. Use it to render a "Start a new conversation" action for errors where retrying fails again.
  * `assistantMessageLeadingComponent`. Assistant message: custom leading component, such as an avatar.
  * `assistantMessageFooterComponent`. Assistant message: custom footer component.
  * `userMessageLeadingComponent`. User message: custom leading component, such as an avatar.
  * `userMessageFooterComponent`. User message: custom footer component.
  * `promptFooterComponent`. Prompt: custom footer component.
  * `promptHeaderComponent`. Prompt: custom header component.
  * `suggestionsComponent`. Custom component to render [follow-up prompt suggestions](/doc/guides/algolia-ai/agent-studio/how-to/agent-configuration#follow-up-prompt-suggestions). Receives a prop containing `{ suggestions: string[], onSuggestionClick: (str: string) => void }`. The widget renders it once the response finishes, so no suggestion placeholder competes with the streaming answer, and the component doesn't receive a loading state.

  To customize the button that opens the chat, use the [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react) widget's `toggleIconComponent` prop instead.

  To add a disclaimer or policy notice, see [Add a legal notice to the chat widget](/doc/guides/building-search-ui/going-further/chat-customization/legal-notice/react).

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    // ...
    headerTitleIconComponent={() => <span>✨</span>}
    headerMaximizeIconComponent={({ maximized }) => (
      <span>{maximized ? "🔽" : "🔼"}</span>
    )}
    promptFooterComponent={() => (
      <a href="https://example.com/privacy-policy">Privacy policy</a>
    )}
  />
  ```
</ParamField>

<ParamField body="messagesProps" type="object">
  Props forwarded to the messages section of the widget. Use `assistantMessageProps` and `userMessageProps` to customize each role separately:

  * `textComponent`. A component that renders each text part of the message. It replaces the default markdown rendering. It receives:
    * `part`. The text part to render. Its `text` property holds the text content.
    * `message`. The message the text part belongs to.
    * `messages`. The full conversation, when available.
    * `status`. The current chat status: `'ready'`, `'submitted'`, `'streaming'`, or `'error'`.
    * `partIndex`. The index of the text part in the message's `parts` array.
  * `parseMarkdown`. The default value is `true`. The widget renders text and reasoning parts as Markdown, including links, code blocks, and emphasis.
    Set `parseMarkdown` to `false` to render reasoning parts and text parts that use the default renderer as plain text.
    Line breaks are preserved and links aren't clickable.
    Use this option for user messages when Markdown characters such as `*` or `_` should remain literal.
    If you provide a `textComponent`, it takes precedence for text parts. `parseMarkdown` then only controls reasoning parts.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    // ...
    messagesProps={{
      assistantMessageProps: {
        textComponent: ({ part, status }) => (
          <span className="MyAssistantText" data-status={status}>
            {part.text}
          </span>
        ),
      },
      userMessageProps: {
        textComponent: ({ part }) => (
          <span className="MyUserText">{part.text}</span>
        ),
      },
    }}
  />
  ```

  To render user messages as plain text while keeping the default Markdown rendering for assistant messages:

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    // ...
    messagesProps={{
      userMessageProps: {
        parseMarkdown: false,
      },
    }}
  />
  ```
</ParamField>

<ParamField body="classNames" type="Partial<ChatClassNames>">
  The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements.
  It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com).

  * `root`. The root element of the widget.
  * `container`. The container element.
  * `header`. The header section of the widget.
    * `root`. The root element.
    * `clear`. The clear button.
    * `close`. The close button.
    * `maximize`. The maximize button.
    * `title`. The title element.
    * `titleIcon`. The title icon element.
  * `messages`. The messages section of the widget.
    * `root`. The root element.
    * `content`. The scrollable content.
    * `scroll`. The scroll container.
    * `scrollToBottom`. The scroll to bottom button.
    * `scrollToBottomHidden`. The hidden state of the scroll to bottom button.
  * `message`. The message in the messages section.
    * `root`. The root element.
    * `container`. The message container.
    * `leading`. The leading element, such as an avatar.
    * `content`. The content element.
    * `message`. The message text element.
    * `reasoning`. The reasoning disclosure.
    * `reasoningHeader`. The reasoning disclosure header.
    * `reasoningIcon`. The reasoning icon.
    * `reasoningLabel`. The reasoning label.
    * `reasoningChevron`. The reasoning disclosure chevron.
    * `reasoningBody`. The reasoning body.
    * `reasoningText`. The reasoning text.
    * `actions`. The action buttons container.
    * `footer`. The footer element.
  * `prompt`. The prompt section of the widget.
    * `root`. The root element.
    * `actions`. The actions container.
    * `body`. The body element.
    * `footer`. The footer element.
    * `header`. The header element.
    * `submit`. The submit button.
    * `textarea`. The text area element.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    classNames={{
      root: "MyCustomChat",
      container: "MyCustomChatContainer MyCustomChatContainer--subclass",
      header: {
        root: "MyCustomChatHeader",
        title: ["MyCustomChatHeaderTitle", "MyCustomChatHeaderTitle--subclass"],
        // ...
      },
      // ...
    }}
  />
  ```
</ParamField>

<ParamField body="translations" type="Partial<ChatTranslations>">
  A dictionary of translations to customize the UI text and support internationalization.

  * `header`. The header section of the widget.
    * `clearLabel`. Accessible label for the clear button.
    * `closeLabel`. Accessible label for the close button.
    * `maximizeLabel`. Accessible label for the maximize button.
    * `minimizeLabel`. Accessible label for the minimize button.
    * `title`. Title to display.
  * `messages`. The messages section of the widget.
    * `copyToClipboardLabel`. Accessible label for the copy to clipboard action.
    * `feedbackThankYouText`. Text shown after feedback is submitted (default: `'Thanks for your feedback!'`).
    * `loaderText`. Text to display in the loader. Pass a function to customize the text based on the turn context, for example `({ phase }) => phase === 'tool' ? 'Searching...' : 'Thinking...'`.
    * `regenerateLabel`. Accessible label for the regenerate action.
    * `scrollToBottomLabel`. Accessible label for the scroll to bottom button.
    * `sendingFeedbackLabel`. Accessible label for the feedback spinner (default: `'Sending feedback...'`).
    * `thumbsDownLabel`. Accessible label for the thumbs down button (default: `'Dislike'`).
    * `thumbsUpLabel`. Accessible label for the thumbs up button (default: `'Like'`).
  * `message`. Individual message in the messages section.
    * `messageLabel`. Accessible label for the message.
    * `actionsLabel`. Accessible label for the actions container.
    * `reasoningLabel`. Label for a reasoning disclosure.
    * `toolErrorRetryText`. Text for the retry button of a failed tool call (default: `'Retry'`). The button only shows for tools with [`retryOnError: true`](#param-tools).
  * `prompt`. The prompt section of the widget.
    * `disclaimer`. Disclaimer text shown in the prompt footer.
    * `emptyMessageTooltip`. The tooltip for the submit button when message is empty.
    * `sendMessageTooltip`. The tooltip for the send button.
    * `stopResponseTooltip`. The tooltip for the stop button.
    * `textareaLabel`. Accessible label for the text area.
    * `textareaPlaceholder`. Placeholder text for the text area.

  To add richer prompt or message notices, see [Add a legal notice to the chat widget](/doc/guides/building-search-ui/going-further/chat-customization/legal-notice/react).

  ```jsx JavaScript icon=code theme={"system"}
  <Chat
    translations={{
      header: {
        title: "My AI shopping assistant",
        closeLabel: "Close chat",
        // ...
      },
      // ...
    }}
  />
  ```
</ParamField>

<ParamField body="...props" type="React.ComponentProps<'div'>">
  Any `div` prop to forward to the root element of the widget.

  ```jsx JavaScript icon=code theme={"system"}
  <Chat className="MyCustomChat" title="My custom title" />
  ```
</ParamField>

## Hook

Use `useChat` to build a chat interface with your own React components.
The Hook connects to InstantSearch and creates a chat instance unless you provide one.
Render your custom component inside `InstantSearch` in place of `Chat`.

* For a chat panel, provide a way to open it, such as [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react) or a custom component built with `useChatTrigger`.
* If you display the chat inline or use a custom opener that InstantSearch doesn't recognize, set `disableTriggerValidation: true`.

```jsx JavaScript icon=code expandable theme={"system"}
import { useChat } from "react-instantsearch";

function CustomChat({ agentId }) {
  const {
    messages,
    input,
    setInput,
    sendMessage,
    status,
    stop,
    error,
    clearError,
  } = useChat({ agentId, disableTriggerValidation: true });

  return (
    <section>
      {messages.map((message) => (
        <div key={message.id}>
          <strong>{message.role}</strong>
          {message.parts.map((part, index) =>
            part.type === "text" ? <p key={index}>{part.text}</p> : null
          )}
        </div>
      ))}
      {status === "error" && error && <p role="alert">{error.message}</p>}
      <form
        onSubmit={(event) => {
          event.preventDefault();
          if (input.trim() && (status === "ready" || status === "error")) {
            clearError();
            sendMessage({ text: input });
            setInput("");
          }
        }}
      >
        <label htmlFor="chat-prompt">Message</label>
        <input
          id="chat-prompt"
          value={input}
          onChange={(event) => setInput(event.target.value)}
        />
        <button
          type="submit"
          disabled={!input.trim() || (status !== "ready" && status !== "error")}
        >
          Send
        </button>
        {status === "streaming" && (
          <button type="button" onClick={stop}>Stop</button>
        )}
      </form>
    </section>
  );
}
```

### Parameters

Pass one configuration object to `useChat`.
Provide [`agentId`](#param-agent-id) or [`transport`](#param-transport), or pass a preconfigured `chat` instance.
For shared options such as `context`, `tools`, and `persistence`, see [Props](#props).
If you pass `chat`, configure its transport and message persistence on the instance.

You can set `id` and `messages` to initialize the conversation.
When the Hook creates the chat instance, it also accepts `generateId`, `onError`, and `onData`.
Changing option values or callback functions can replace the Hook's internal chat instance and clear conversation state that isn't persisted.

### APIs

`useChat` returns the following state values and methods:

* `id` (`string`): conversation identifier.
* `messages` (`UIMessage[]`): conversation messages, including streamed message parts.
* `status` (`'ready' | 'submitted' | 'streaming' | 'error'`): request status. Clear an error with `clearError` before sending another message.
* `error` (`Error | undefined`): last request error.
* `input` (`string`): current text in the message input. Sending a message doesn't clear this value automatically.
* `setInput(input: string)`: update the text in the message input.
* `open` (`boolean`), `setOpen(open: boolean)`, and `focusInput()`: panel state and controls.
  `focusInput` opens the panel and requests prompt focus. Handle DOM focus in a custom interface.
* `sendMessage(message, options?)`: add a user message and request an assistant response. For text, pass `{ text: "MESSAGE" }`. Returns `Promise<void>`.
* `regenerate(options?)`: regenerate an assistant response. Pass `{ messageId }` to select a message, or omit it to regenerate the latest assistant message. Returns `Promise<void>`.
* `stop()`: stop the active response. The `onFinish` callback receives `isAbort: true`.
* `resumeStream(options?)`: reconnect to an active response. Set [`resume`](#param-resume) to `true` to resume when the Hook initializes.
* `clearError()`: clear the last request error.
* `setMessages(messagesOrUpdater)`: replace the local message list, or update it with a function. Call `regenerate` to request a response after editing messages.
* `clearMessages()`: stop an active response, clear messages and feedback, and start a new conversation ID.
* `addToolResult(options)`: submit the result of a client-side tool call.
  Pass `tool` and `toolCallId`, with either `output` or `state: 'output-error'` and `errorText`.
* `tools` (`ClientSideTools`): configured tools with `addToolResult` and search helpers bound for rendering.
* `records` (`ChatRecordsStore`): records returned by chat tools, keyed by `objectID`.
* `suggestions` (`string[] | undefined`): suggestions from the last assistant message's `data-suggestions` part.
* `sendChatMessageFeedback(messageId, vote)`: send feedback on an assistant message. Pass `0` for negative feedback or `1` for positive feedback. Available only when you provide an `agentId` and set `feedback: true`.
* `feedbackState` (`Record<string, 'sending' | 0 | 1>`): feedback state by message ID.
* `indexUiState` and `setIndexUiState(nextUiState)`: read or update the parent index UI state.
* `sendEvent(...)`: send a chat event with the InstantSearch Insights middleware.

`sendMessage`, `regenerate`, and `resumeStream` accept request options for `headers`, `body`, and `metadata`.
During server rendering and hydration, the Hook presents a closed chat with no messages and `status: 'ready'` until the browser initializes it.

## Streaming and resumption

Assistant responses stream over [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events).
The `useChat` Hook exposes the streaming lifecycle if you want to drive a custom UI.

### Status values

`useChat` returns a `status` value, which can be one of:

* `'ready'`. The chat is idle and ready to accept a new message.
* `'submitted'`. A user message was submitted and the assistant hasn't started responding yet.
* `'streaming'`. The assistant is streaming a response.
* `'error'`. The last response finished with an error. Call `clearError()` before sending a new message.

Each message part also carries a `state` of `'streaming'` or `'done'` while the response streams.

### Stop and resume

`useChat` also returns:

* `stop()`. Aborts the current streaming response. The `onFinish` callback runs with `isAbort: true`.
* `resumeStream()`. Reconnects to an in-flight stream. Call this on mount, or pass [`resume`](#param-resume) to `Chat` to do it automatically.

```jsx JavaScript icon=code theme={"system"}
import { useChat } from "react-instantsearch";

function StopButton() {
  const { status, stop } = useChat({ agentId: "YOUR_AGENT_ID" });

  if (status !== "streaming") {
    return null;
  }

  return <button onClick={() => stop()}>Stop</button>;
}
```

To resume an ongoing stream after a reload, set [`resume`](#param-resume) on `Chat`, or call `resumeStream()` from a custom component.
