A surface is a place where the conversation appears. There are four, and they all show the same conversation:

| Surface         | What it is                                                                                                       | Comes with                                      |
| --------------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| Corner widget   | Your mark in a corner of the screen. Clicking it opens the drawer                                                | `TeaflaskAssistant`                             |
| Drawer          | The conversation in a panel. On wide screens the user can switch it between a floating card and a docked sidebar | `TeaflaskAssistant`                             |
| Command palette | A centered dialog over your page, opened with ⌘K or Ctrl+K                                                       | `TeaflaskAssistant`                             |
| Full page       | The conversation filling a box on a route of your own, with a history menu                                       | `AssistantPage`, or `<teaflask-assistant-page>` |

Most apps need only `TeaflaskAssistant`, mounted once as described in [Install](/install). Add the full page if you want a dedicated `/help` or `/assistant` route.

## How surfaces share a conversation

Surfaces on a page share one live conversation when they have the same three things:

- the publishable key,
- the API address (`baseUrl`), and
- the signed-in user (`userId`), or no user at all.

If any of the three differs, they're separate conversations. A reply streaming in the drawer also streams into an open full page, and the conversation stays live as long as at least one matching surface is mounted. That's why [Install](/install) asks you to mount the widget in your app shell: when you open the full page on another route, the widget is still mounted and nothing reconnects.

Two consequences:

- Wire identity the same way on every surface. A widget with `userId` and a full page without it are two conversations, which looks to the user like the assistant forgot what they just said.
- Surfaces share only within one copy of the assistant. An npm component and a script-tag element on the same page are two copies and two conversations.

Surfaces also make room for each other. While a full page is mounted, the corner widget hides. While the palette is open, the widget hides too.

## The command palette

The palette opens with ⌘K on macOS and Ctrl+K elsewhere. If your app already uses that shortcut, change it or turn it off:

| You want             | npm              | Script tag       |
| -------------------- | ---------------- | ---------------- |
| A different shortcut | `hotkey="mod+j"` | `hotkey="mod+j"` |
| No shortcut          | `hotkey={false}` | `hotkey="false"` |

`mod` means ⌘ on macOS and Ctrl elsewhere. Any combination works, such as `"ctrl+shift+p"`. A value the assistant can't read turns the shortcut off and logs a console warning. It doesn't fall back to ⌘K, because that's likely the shortcut you were trying to avoid. A key press that your own handler has already handled with `preventDefault()` is ignored.

To open the palette from your own button, call `openPalette()`. It works even when the shortcut is off. In React, it's on a ref to `TeaflaskAssistant`:

```tsx
import { useRef } from "react";
import {
  TeaflaskAssistant,
  type TeaflaskAssistantHandle,
} from "@teaflask/assistant";

export function AssistantWithButton() {
  const assistant = useRef<TeaflaskAssistantHandle>(null);

  return (
    <>
      <button onClick={() => assistant.current?.openPalette()}>
        Ask for help
      </button>
      <TeaflaskAssistant
        ref={assistant}
        publishableKey="pk_live_…"
        hotkey={false}/>
    </>
  );
}
```

On the script tag, `openPalette()` is a method on the element. It's available once the element has loaded, so wait for that first:

```js
await customElements.whenDefined("teaflask-assistant");
document.querySelector("teaflask-assistant").openPalette();
```

## The corner widget and drawer

The widget sits in the bottom-right corner. Set `corner` to `"bottom-left"` to move it and its drawer. The drawer's floating and sidebar layouts are the user's choice, made in the drawer itself, so there's nothing for you to configure. Small screens always get the floating layout.

The drawer's welcome screen shows opening prompts. By default these are the prompts configured for your agent in Teaflask. Set `suggestions` to replace them. Any value replaces them entirely, including an empty list, which shows none:

```tsx
<TeaflaskAssistant
  publishableKey="pk_live_…"
  suggestions={[
    "How do I invite a teammate?",
    { prompt: "Review my billing settings", kind: "review" },
  ]}/>
```

A prompt is a string, or an object whose `kind` picks its icon: `"ask"` (the default), `"review"`, `"write"` or `"learn"`. On the script tag, `suggestions` is a property.

## The full page

The full page fills whatever box you put it in. It has no height of its own, so give its container one. A container with no height shows nothing, and that's the most common mistake with this surface.

In React, render `AssistantPage` inside `TeaflaskAssistant` or inside a `TeaflaskAssistantProvider`. If your app shell already mounts `TeaflaskAssistant`, as Install recommends, the page needs a provider of its own on its route. Give it the same key and identity as the widget, so both resolve to the same conversation:

```tsx
import {
  AssistantPage,
  TeaflaskAssistantProvider,
} from "@teaflask/assistant";

export function HelpRoute() {
  return (
    <TeaflaskAssistantProvider publishableKey="pk_live_…">
      <div style={{ height: "calc(100dvh - 64px)" }}>
        <AssistantPage title="Acme Help"/>
      </div>
    </TeaflaskAssistantProvider>
  );
}
```

The `64px` stands for your own header. Use whatever height your layout leaves for the page.

On the script tag, the loader you already added also defines the page element:

```html
<div className="help-page">
  <teaflask-assistant-page
    publishable-key="pk_live_…"
    heading="Acme Help"></teaflask-assistant-page>
</div>
```

Size `.help-page` in your stylesheet. On the element, the title attribute is called `heading`. That's because the standard HTML `title` attribute would show a tooltip over the whole page.

### Full-page settings

| Setting         | npm prop        | Element                | What it does                                                                                                  |
| --------------- | --------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
| Title           | `title`         | `heading` attribute    | The name in the page's header. Defaults to "Assistant"                                                        |
| Frameless       | `frameless`     | `frameless` attribute  | Removes the page's border and rounded corners, so it sits flush inside a frame you already draw               |
| Opening prompts | `suggestions`   | `suggestions` property | Prompts under the empty message box. The full page shows none unless you set them                             |
| First message   | `initialPrompt` | —                      | Sent for the user when the page opens on an empty conversation, at most once per mount. Useful for deep links |
| Welcome heading | `welcomeTitle`  | —                      | The heading above the empty message box. Defaults to "Where should we begin?"                                 |
| Welcome mark    | `welcomeMark`   | —                      | Your artwork beside that heading                                                                              |
| Your own header | `renderChrome`  | —                      | Replaces the page's header with controls you draw. Return a portal to put them in a bar you already have      |

The full page has its own history menu, which opens from the title. History exists only for signed-in users, so anonymous visitors see no menu. See [Identity](/identity).

The full page has no corner widget, palette or shortcut. Keep `TeaflaskAssistant` in your shell if you want those on other routes.

## Composing surfaces yourself

`TeaflaskAssistant` is a provider plus the widget, drawer and palette. To place these pieces separately, mount `TeaflaskAssistantProvider` and render the ones you want inside it: `AssistantCompanion` for the corner widget and drawer, `AssistantPalette` for the palette (you control whether it's open), and `AssistantPage`. To replace the interface entirely, see [Headless](/headless).

## More resources

- [Install](/install) – mounting the widget once in your app shell
- [Identity](/identity) – why every surface needs the same identity wiring to share a conversation
- [Theming](/appearance) – theming the widget and the full page alike
- [Headless](/headless) – building your own interface instead of using these surfaces
- [Transcript](/transcript) – what each surface shows as the conversation unfolds
