# Interface modules (/docs/modules)

The interface modules are the AI features a site adds in a few lines, with no interface to build. Each one runs the conversation on the visitor's own AI through AI Wallet, calls your site's tools from your MCP server, and draws the whole exchange.

| Form | React | Web Component | Page |
| --- | --- | --- | --- |
| A chat docked in a side panel at the page's edge | `AiChatPanel` | `<aiconnect-chat variant="panel">` | [Side panel](/docs/modules/panel) |
| A floating button that opens a chat popup | `AiChatPopup` | `<aiconnect-chat variant="popup">` | [Popup](/docs/modules/popup) |
| A chat inline in the page | `AiChatInline` | `<aiconnect-chat variant="inline">` | [Inline chat](/docs/modules/inline) |
| A full page AI mode | `AiChatPage` | `<aiconnect-chat variant="page">` | [Full page](/docs/modules/page) |
| A search bar that answers | `AiSearch` | `<aiconnect-search>` | [Search bar](/docs/modules/search) |

The React components come with `@aiconnect/react`. The Web Components are the same modules in one script for sites without React, such as WordPress pages: see [Web Components](/docs/modules/web-components).

## What every module does [#what-every-module-does]

- **Streams the answer.** The text appears as the visitor's model writes it, with a cyan caret at its end. Light Markdown (bold, lists, links, code) is drawn as formatting.
- **Shows the tool calls as they run.** Each call of one of your tools gets a row with a cyan light while it runs, a check once its result is back, and the arguments the model chose.
- **Names the account that answers.** The header shows the account and its model as the wallet reports them (`state.providerName` and `state.model`), with the wallet's arcs lit while the answer streams.
- **Handles the connect and install steps.** Until the visitor's AI is connected, the module shows the step the visitor is at: connect the wallet, add it to the browser, approve the site, or try again after a refusal. Its button opens the [connect window](/docs/quickstart/react#add-the-entry-point). A question asked before the connection stays on screen and goes out once the wallet connects the site.
- **Offers Stop.** While an answer streams, the send button becomes Stop. Stopping ends the answer in the wallet too and keeps the text written so far.
- **Follows your design system.** Every color, the font, the radii and the sizes come from the `--aic-*` tokens, in light, dark and right to left. See [Theming the modules](#theming-the-modules).
- **Speaks English, French and Hebrew.** The provider's `locale` sets the language of the modules and of the connect window. Hebrew lays the module out right to left, and the side panel and the popup move to the left edge.

## Quickstart [#quickstart]

Install the packages and wrap your app in `AiConnectProvider`, as in the [React quickstart](/docs/quickstart/react). The stylesheet `@aiconnect/react/styles.css` carries the modules' styles with the connect window's.

```bash
npm install @aiconnect/core @aiconnect/react
```

Then render a module where it belongs. This popup answers from the tools of your MCP server:

```tsx title="src/App.tsx"
import { AiChatPopup } from '@aiconnect/react';

const SYSTEM = 'You recommend books from this site\'s catalog. Always search the catalog before recommending.';

export function App() {
  return (
    <>
      <YourSite />
      <AiChatPopup system={SYSTEM} mcp="/api/mcp" suggestions={['A calm book for tonight']} />
    </>
  );
}
```

```tsx title="src/main.tsx"
import { AiConnect } from '@aiconnect/core';
import { AiConnectProvider } from '@aiconnect/react';
import '@aiconnect/react/styles.css';

const client = new AiConnect();

createRoot(document.getElementById('root')!).render(
  <AiConnectProvider client={client} locale="en">
    <App />
  </AiConnectProvider>,
);
```

The module needs nothing else: the visitor clicks the floating button, connects their AI from the popup the first time, and asks. A visitor who does not have the wallet yet gets a link to the AI Wallet page.

In a Next.js App Router site, render the modules from a client component inside your provider, as the [Next.js quickstart](/docs/quickstart/nextjs) does for the provider itself.

<Callout type="info">
`mcp` takes the address of your MCP server, and the module connects to it when the first question is sent. A site without an MCP server passes `tools` instead: any object with `listTools` and `callTool`, as the [React quickstart](/docs/quickstart/react#try-it-without-a-server) shows. The [MCP guide](/docs/guides/mcp-tools) explains how to serve your tools.
</Callout>

## Props every module takes [#props-every-module-takes]

### AiModuleProps

| Prop                 | Type                                           | Description |
| -------------------- | ---------------------------------------------- | ----------- |
| `system`             | `string`                                       |             |
| `tools?`             | `ToolSource \| undefined`                      |             |
| `mcp?`               | `string \| undefined`                          |             |
| `title?`             | `string \| undefined`                          |             |
| `intro?`             | `string \| undefined`                          |             |
| `placeholder?`       | `string \| undefined`                          |             |
| `suggestions?`       | `string[] \| undefined`                        |             |
| `toolLabels?`        | `Record<string, string> \| undefined`          |             |
| `renderToolResults?` | `((turn: ChatTurn) => ReactNode) \| undefined` |             |
| `className?`         | `string \| undefined`                          |             |

- **system.** The instructions the visitor's model gets before the first question: what the site is, which tools to use and how to answer.
- **tools** and **mcp.** Where your tools come from. Give one of them. Keep a `tools` object stable across renders, for example by building it once outside the component.
- **title**, **intro** and **placeholder.** Your own words for the header, for the heading of an empty conversation, and for the question box. Without them the module uses its built in copy in the provider's language.
- **suggestions.** Questions shown in an empty conversation, sent with one click.
- **toolLabels.** A plain name per tool, shown while it runs, such as `{ search_books: 'Searching the catalog' }`. Without one, the row shows the tool's own name.
- **renderToolResults.** Draws a turn's tool results your way. See [Your own result cards](#your-own-result-cards).

The side panel, the popup and the full page also take how they open:

### AiOverlayProps

| Prop             | Type                                     | Description |
| ---------------- | ---------------------------------------- | ----------- |
| `open?`          | `boolean \| undefined`                   |             |
| `defaultOpen?`   | `boolean \| undefined`                   |             |
| `onOpenChange?`  | `((open: boolean) => void) \| undefined` |             |
| `hideLauncher?`  | `boolean \| undefined`                   |             |
| `launcherLabel?` | `string \| undefined`                    |             |

Leave `open` out to let the module keep its state. Give `open` and `onOpenChange` to open it from your own button, and `hideLauncher` to hide the module's own.

## Your own result cards [#your-own-result-cards]

`renderToolResults` receives each turn, with the visitor's question, the answer so far and every tool call with its raw result, and returns what to draw under the answer. It runs again as the answer streams, so cards can follow the names the answer gives.

Taamtaam draws the restaurants its catalog search returned that the answer names, in the order the answer names them:

```tsx title="AiModePanel.tsx"
import type { ChatTurn } from '@aiconnect/react';

function renderRestaurants(turn: ChatTurn) {
  const restaurants = suggestedRestaurants(turn);
  if (restaurants.length === 0) {
    return null;
  }
  return (
    <ul className={styles.suggestions}>
      {restaurants.map((restaurant) => <li key={restaurant.id}><RestaurantCard restaurant={restaurant} /></li>)}
    </ul>
  );
}

<AiChatPanel system={SYSTEM_PROMPT} mcp="/api/mcp" renderToolResults={renderRestaurants} />;
```

The cards render inside the module, so your own stylesheet styles them.

## Languages [#languages]

`AiConnectProvider` takes `locale` (`en`, `fr` or `he`) for the connect window and the modules together. The modules' built in copy is `builtInModuleMessages`. Pass `moduleMessages` to the provider to write the whole copy yourself, and set its `dir` to `rtl` for a right to left language.

### ModuleMessages

| Prop                | Type             | Description |
| ------------------- | ---------------- | ----------- |
| `dir`               | `"ltr" \| "rtl"` |             |
| `title`             | `string`         |             |
| `pageTitle`         | `string`         |             |
| `intro`             | `string`         |             |
| `placeholder`       | `string`         |             |
| `searchPlaceholder` | `string`         |             |
| `searchLabel`       | `string`         |             |
| `suggestionsLabel`  | `string`         |             |
| `send`              | `string`         |             |
| `stop`              | `string`         |             |
| `stopped`           | `string`         |             |
| `newChat`           | `string`         |             |
| `close`             | `string`         |             |
| `clear`             | `string`         |             |
| `openChat`          | `string`         |             |
| `closeChat`         | `string`         |             |
| `question`          | `string`         |             |
| `answersWith`       | `string`         |             |
| `thinking`          | `string`         |             |
| `toolRunning`       | `string`         |             |
| `toolDone`          | `string`         |             |
| `toolFailed`        | `string`         |             |
| `connect`           | `string`         |             |
| `installText`       | `string`         |             |
| `pendingText`       | `string`         |             |
| `retry`             | `string`         |             |

The modules also show the connect window's own copy for the connection steps, from `messages`.

## Theming the modules [#theming-the-modules]

The modules read the tokens of the [theming guide](/docs/guides/theming) and these ones of their own:

| Token | Light | Dark | What it styles |
| --- | --- | --- | --- |
| `--aic-mono` | `'Reddit Mono', ui-monospace, monospace` | same | Tool names, the model's name and code in answers. |
| `--aic-stripe` | `#15161a` | `#050506` | The stripe of the key card beside the account that answers. |
| `--aic-fill` | ink at 7% over the surface | same rule | The visitor's question and the model's tag. Set it to override the mix. |
| `--aic-ink-2` | ink at 84% over the surface | same rule | The answer's text. Set it to override the mix. |
| `--aic-panel-width` | `420px` | same | The side panel's width. |
| `--aic-popup-width` | `400px` | same | The popup's width. |
| `--aic-popup-height` | `640px` | same | The popup's height. |
| `--aic-inline-height` | `560px` | same | The inline chat's height. |
| `--aic-page-width` | `760px` | same | The column of the full page mode. |
| `--aic-layer` | `990` | same | The stacking level of the side panel, the popup, its button and the full page. The connect window sits at 1000, above them. |

`--aic-fill` and `--aic-ink-2` are mixed from your `--aic-ink` and `--aic-surface` when you do not set them, so a site that sets its own palette gets matching shades. Dark mode follows `data-aic-theme`, as for the connect window.

Taamtaam's theme sets its font, its warm palette and a wider panel:

```css title="aiConnectTheme.css"
:root {
  --aic-font: var(--font-montserrat), sans-serif;
  --aic-ink: #2f2b22;
  --aic-muted: #5c5544;
  --aic-ground: #f7f5f0;
  --aic-border: #e2ddd1;
  --aic-accent: #6f6754;
  --aic-panel-width: 440px;
}
```

<Callout type="warn">
The React modules render in your page, so your element styles do not reach them, but a rule that styles every `p` or `ul` inside a container you render a module in reaches the module's text too. Scope such rules to your content (for example `.article > p`), or use the [Web Components](/docs/modules/web-components), whose shadow root keeps page styles out.
</Callout>

## Next steps [#next-steps]

- Pick a form: [side panel](/docs/modules/panel), [popup](/docs/modules/popup), [inline chat](/docs/modules/inline), [full page](/docs/modules/page) or [search bar](/docs/modules/search).
- Add the modules to a page without React with [one script tag](/docs/modules/web-components).
- Serve your tools with the [MCP guide](/docs/guides/mcp-tools).
