# Chat integration

Compose an inline, floating or fullscreen conversation with the shared components. The toolkit owns presentation, dialog state, focus, scrolling primitives and composer input. The application owns messages, requests, loading and error state, sanitization and persistence. No AI provider or separate chat stylesheet is required.

## Floating conversation

Load `@dg/ui/styles.css` in the root layout. This complete local example records a user message and an explicitly labeled demonstration reply. Replace the reply with an app-owned backend request when integrating a service.

```svelte
<script>
  import { tick } from "svelte";
  import { ChatWindow, ChatPanel, ChatThread, ChatMessage, ChatComposer } from "@dg/ui";

  let draft = $state("");
  /** @type {{ id: number, role: 'user' | 'assistant', text: string }[]} */
  let messages = $state([]);
  /** @type {ReturnType<typeof ChatThread>} */
  let thread;
  let nextId = 0;

  async function send(text) {
    messages.push({ id: nextId++, role: "user", text });
    draft = "";
    messages.push({ id: nextId++, role: "assistant", text: "Demo reply: your question was received locally." });
    await tick();
    thread.scrollToBottom();
  }
</script>

<ChatWindow id="support-chat" title="Support">
  <ChatPanel>
    <ChatThread bind:this={thread}>
      {#each messages as message (message.id)}
        <ChatMessage role={message.role} text={message.text} />
      {/each}
    </ChatThread>
    <ChatComposer id="support-question" bind:value={draft} onsend={send} />
  </ChatPanel>
</ChatWindow>
```

[`ChatWindow`](../components/chat-window.md) provides its own launcher, header and controls. Its methods open, close and change the layout through `bind:this`. Children remain mounted while closed, so closing preserves their state; route unmounts and page reloads require application persistence.

For an inline conversation, omit `ChatWindow` and place [`ChatPanel`](../components/chat-panel.md) inside a flex-column parent with a bounded height. This gives [`ChatThread`](../components/chat-thread.md) a scrolling viewport. The [working demo](../../../../apps/ds/src/lib/docs/components/ChatDemo.svelte) shows both compositions.

## Request lifecycle

[`ChatComposer`](../components/chat-composer.md) sends trimmed nonempty text on Enter or form submission, permits Shift+Enter newlines and ignores IME composition. It does not clear its own draft or await the result of `onsend`. The parent clears `value`, guards concurrent sends and passes `disabled` while a request is unavailable. For an active request, pass `pending` and `onstop`: the draft becomes read-only and keeps focus, and the send control becomes a stop control. The parent aborts its request and clears `pending`; after a stop, focus returns to the draft. Control names follow `Page`'s `lang`.

Append messages with stable identifiers, await `tick()` and call the thread's scrolling methods when needed. Scrolling is explicit by default. Pass `autoscroll={pending}` to follow streamed content until the reader uses the wheel, touch, scrolling keys or scrollbar. Following stays suspended for that response. Before appending the next turn, `isAtBottom()` lets an application resume following only when the reader has returned to the latest content. Set `aria-busy={pending}` on `ChatThread` while a reply streams, so assistive technology reads the finished message rather than each fragment; announce progress and completion in a separate status region outside the thread.

Place the empty state, failures and their retry action as the last children of `ChatThread`, where they scroll with the messages and stay clear of the composer's fade. Show a failure as a `.feedback` paragraph and a [`Button`](../components/button.md); a retry reuses the existing user message instead of appending a new one. A `ChatSuggestions` selection sends its text directly and leaves the draft untouched.

Plain `ChatMessage.text` is escaped by Svelte. A `children` snippet replaces it for rich content. The application must parse Markdown and sanitize untrusted HTML before rendering it, and validate any source links it creates. `source` is short display text, such as an attribution or a label like “Simulated reply”, not a citation retrieval mechanism.

## Supporting components

| Need                 | Component                                              |
| -------------------- | ------------------------------------------------------ |
| Suggested prompts    | [`ChatSuggestions`](../components/chat-suggestions.md) |
| Message presentation | [`ChatMessage`](../components/chat-message.md)         |

An empty state, the current context or an inline title are ordinary app markup inside `ChatPanel`: a heading, a paragraph and native buttons.

`onsend(text)` and `onselect(text)` receive strings directly. DOM callbacks receive browser events; action callbacks are described in their component references. Keep IDs unique, including across inline and floating instances. Built-in control names follow `Page`'s `lang`; pass labels only to name your own content.

## Layout and focus

Floating mode uses a nonmodal native dialog; fullscreen uses a modal dialog and locks page scrolling. Opening focuses the first input, changing layout focuses the resize control and closing restores the launcher. Fullscreen is an application layout, not the browser Fullscreen API. Verify Escape, repeated opening, focus restoration and draft preservation in the actual app.

Headers, threads, messages and composers use [content insets and gaps](../foundations/spacing.md#functional-spacing); control groups use control gaps. The textarea has no inline padding and centers a single line with block padding derived from the minimum control height and its line height; its rounded input row owns the outer field insets, and the form combines the thread and message insets at the inline start to align with assistant text, and uses only the thread inset at the inline end to align with the outer user bubble edge. Message and entry surfaces keep their own radii, outside the [shared capsule](../foundations/surfaces.md#capsules); bubble widths, circular controls and window dimensions remain local. Fullscreen centers the panel through internal CSS variables; do not override those variables as an app API.
