# Embed the chat

Add ShappyChat to your web app with the SDK. The SDK supplies the chat UI. A runtime does the work. See [Runtimes](/docs/runtimes).

## Get the SDK files

The SDK package is `@shappychat/chat`. It is not on npm. Your Shappy Backend supplies it. In the folder of your app, run this command:

```bash
npm install https://chat.example.com/sdk/shappychat.tgz
```

Use the public URL of your backend. Step 6 of the backend setup shows this command with your URL. The release also has this file as `shappychat-<version>.tgz`.

If you use TypeScript, set `"skipLibCheck": true` in your `tsconfig.json`. The SDK types refer to a package that the private file does not include.

To build the SDK from the source checkout:

```bash
./app.sh build
```

The build writes the SDK to `sdk-dist/`. Link that folder in your project, or serve these files with your app:

| File | Contents |
| --- | --- |
| `sdk-dist/embed.js` | The browser entry point |
| `sdk-dist/style.css` | The chat styles. They apply only inside the chat, so they do not change the styles of your page. |
| `sdk-dist/node.js` | The Node entry point (`@shappychat/chat/node`) |

## Mount the chat panel

Use `mountShappyChatDrawer`. It supplies the full chat surface with the ShappyChat header and launchers.

```ts
import { mountShappyChatDrawer, RemoteShappyChatRuntime } from '@shappychat/chat';
import '@shappychat/chat/style.css';

const runtime = await RemoteShappyChatRuntime.connect({
  url: backendUrl,
  token: appCredential,
  conversationId,
});

const drawer = mountShappyChatDrawer(document.body, {
  runtime,
  presentation: 'drawer',
  trigger: chatButton,
  onOpenChange: (open) => chatButton.setAttribute('aria-expanded', String(open)),
});
chatButton.addEventListener('click', drawer.toggle);
```

> **Warning:** Do not put an app credential in a public bundle. Get it from your server for the signed-in user.

To mount only the conversation in a container that you own, use `mountShappyChat(container, { runtime })`.

## Choose a presentation

![Four small windows: drawer, a panel on the right; floating, a window in the corner; island, a message bar at the bottom; inline, the chat fills a container in your layout](/docs/diagrams/presentations.svg "The four presentations")

| Value | Closed | Open |
| --- | --- | --- |
| `drawer` (default) | Hidden | A panel on the right side of the window |
| `floating` | A round button in the bottom-right corner | A floating chat window |
| `island` | A round button at the bottom centre | A message bar. The conversation shows above it when it has messages. |
| `inline` | Hidden | The chat fills a container in your layout |

In the island, the ghost button folds the conversation. While the conversation is folded, a new message does not open it. Above the message bar, the latest status and the full reply show. A long reply scrolls in that area. Click it, or its chevron, to open the conversation. When the island is closed, its ghost button has no background and half opacity. Thus it does not hide the content of the page.

Change the presentation at any time. The conversation and the unsent text stay.

```ts
drawer.setPresentation('island');
```

## Control the drawer

| Method | Description |
| --- | --- |
| `show()`, `hide()`, `toggle()` | Open or close the chat. Closing does not stop a running reply. |
| `isOpen()` | Returns `true` if the chat is open. |
| `getDraft()`, `setDraft(text)` | Read or set the unsent message. |
| `setRuntime(runtime)` | Show another conversation in the same panel. |
| `setPresentation(value)` | Change the presentation. |
| `setTheme(theme)` | Change the colours. |
| `unmount()` | Remove the UI. The runtime continues. |

## Change the colours

The default theme is ShappyChat purple, in light and dark mode. Set colours with the `theme` option or with `setTheme`.

```ts
const drawer = mountShappyChatDrawer(document.body, {
  runtime,
  theme: { accent: '#5ed6b4', glow: false },
});
```

You can also set CSS custom properties on an ancestor element:

```css
.my-app {
  --shappychat-accent: #5ed6b4;
  --shappychat-panel: #f7f7f2;
  --shappychat-field: #e9e9e1;
  --shappychat-ink: #1f2420;
  --shappychat-ghost: #1f2420;
}
```

| Property | Changes |
| --- | --- |
| `--shappychat-accent` | Buttons, links and highlights |
| `--shappychat-panel` | The chat background |
| `--shappychat-field` | The message field and controls |
| `--shappychat-ink` | The text colour |
| `--shappychat-ghost` | The fill of the ghost mark |

The chat uses dark mode when an ancestor element has the `dark` class.

## Fit the chat to your layout

| Property | Default | Description |
| --- | --- | --- |
| `--shappychat-top` | `0px` | Set this to the height of your header. The panel starts below it. |
| `--shappychat-width` | `440px` | The width of the drawer panel |

Your app controls its own routing and layout. For example, when the drawer opens, you can add space on the right side of your content.

## Drawer options

| Option | Description |
| --- | --- |
| `runtime` | The runtime of the conversation. Required. |
| `presentation` | `drawer`, `floating`, `island` or `inline` |
| `initialOpen` | Open the chat when it mounts. Default `false`. |
| `trigger` | Your chat button. Focus goes back to it when the chat closes. |
| `intro` | An element that shows above the conversation |
| `showHeader` | Set `false` if your app supplies its own toolbar. |
| `theme` | Colour overrides |
| `onOpenChange(open)` | Called when the chat opens or closes |
| `onNewChat()` | Called when the user starts a new chat |
| `onModelSelect(anchor)` | Called when the user selects the model button |
| `onError(error)` | Called when an error occurs |
| `shortcut` | The key that your app binds to open the chat, shown in the island and the launcher. Default `{ label: '⌘/Ctrl K', title: 'Command/Ctrl K' }`. The SDK shows the key. Your app must handle it. |
| `enableAttachments` | Allow file attachments. Default `true`. The chat sends images only to a model that reads images. |
| `showExecutionMode` | Show the Auto, UI and MCP menu. Default `false`; the chat uses Auto. |

## Keyboard

- **Enter** sends the message. **Shift + Enter** adds a new line.
- **Escape** closes the chat. If a menu is open, Escape closes the menu first.
- **Shift + Tab** in the message field changes the execution mode when `showExecutionMode` is on. See [App tools (MCP)](/docs/mcp).

## Let users choose a model

Use `createShappyChatModelPicker`. It opens a menu beside the model button, in the width of the chat.

```ts
import { createShappyChatModelPicker, mountShappyChatDrawer } from '@shappychat/chat';

const picker = createShappyChatModelPicker({
  listModels: () => loadModelsFromYourServer(),
  onError: showError,
});
const drawer = mountShappyChatDrawer(document.body, {
  runtime,
  onModelSelect: (anchor) => anchor && picker.open(anchor, runtime),
});
```

Close the picker before you replace or remove the runtime: `picker.close()`.
