# Attachments flow

Attach files from the composer's paperclip, send them with the prompt, and render them on the user's message — the complete attachment round-trip.

`<kai-prompt-input>` ships with an attach button. Click the paperclip, pick a file, send — `kai-submit` delivers `{ value, attachments }` together, and the user's `<kai-message>` renders the files inline. You own the upload logic; AI/UI handles the staging and display.

The paperclip, the removable chips, and the staged-file state are all built in. You don't wire an upload component for the common case — you just read `kai-submit`.

## How it works

**1 — Read the payload from `kai-submit`**

The composer stages files internally as the user picks them. On submit, `kai-submit` carries the staged list alongside the text — always an array, even when empty:

```js

const prompt = document.getElementById('prompt');

prompt.addEventListener('kai-submit', async (e) => {
  const { value, attachments } = e.detail;
  // attachments: AttachmentData[] — always an array, never undefined

  const aId = crypto.randomUUID();
  chat.messages = [
    ...chat.messages,
    {
      id: crypto.randomUUID(),
      role: 'user',
      parts: [
        // one `file` part per attachment → shown inline on the message
        ...attachments.map((a) => ({ type: 'file', attachment: a })),
        ...(value ? [{ type: 'text', text: value }] : []),
      ],
    },
    { id: aId, role: 'assistant', parts: [] },
  ];
  chat.loading = true;

  // Forward value + attachments to your model API, then stream the reply.
  let answer = '';
  for await (const token of streamFromModel({ value, attachments })) {
    answer += token;
    chat.messages = chat.messages.map((m) =>
      m.id === aId ? { ...m, parts: [{ type: 'text', text: answer }] } : m,
    );
  }
  chat.loading = false;
});
```

`<kai-chat>` carries the same composer and fires the same `kai-submit` — the paperclip works there with zero extra setup too.

**2 — Render attachments on the user message**

Add a `{ type: 'file', attachment }` part per file to the message's `parts` array. `<kai-chat>` and `<kai-message>` render consecutive `file` parts inline beneath the message text — the same display the [`<kai-attachments>`](/components/attachments/) element uses — with no extra wiring:

```js
// The message shape — file parts are optional; omit them for text-only turns.
const userMsg = {
  id: crypto.randomUUID(),
  role: 'user',
  parts: [
    { type: 'file', attachment: { id: 'a1', type: 'file', filename: 'design-spec.pdf', mediaType: 'application/pdf' } },
    { type: 'file', attachment: { id: 'a2', type: 'file', filename: 'screenshot.png',  mediaType: 'image/png' } },
    { type: 'text', text: value },
  ],
};
```

**`AttachmentData` shape:**

| Field | Required | Notes |
|---|---|---|
| `id` | Yes | Stable identifier — used for removals via the chip's remove button |
| `type` | Yes | `'file'` or `'source-document'` |
| `filename` | No | Shown as the chip label |
| `mediaType` | No | MIME type — drives the icon (image / video / audio / document) |
| `url` | No | Object URL or CDN URL — enables image previews in the hover card |
| `title` | No | Display name for `source-document` attachments |

## Pre-populating staged files

Assign `prompt.attachments` in JavaScript after mount to seed the composer before the user types — for files already linked to this conversation, say. The element manages its own staged list from there: the user can add more with the paperclip, remove chips, and `kai-submit` always delivers the current state:

```js
prompt.attachments = [
  { id: 'ctx-1', type: 'file', filename: 'brief.pdf', mediaType: 'application/pdf' },
];
```

## Optional: a dedicated drop zone

When you want a large drop target for a whole page or panel — not just the composer's paperclip — add a standalone `<kai-file-upload>` and feed its files into the composer. The round-trip from `kai-submit` onward is identical; you're only changing how files get staged.

`<kai-file-upload>` fires `kai-files-added` when the user picks or drops files. Convert each `File` to an `AttachmentData` and append it to `prompt.attachments`:

```js
const upload = document.getElementById('upload');
const prompt = document.getElementById('prompt');

upload.addEventListener('kai-files-added', (e) => {
  const current = prompt.attachments ?? [];
  const added = e.detail.files.map((f) => ({
    id: crypto.randomUUID(),
    type: 'file',
    filename: f.name,
    mediaType: f.type || undefined,
    // create an object URL if you want an image preview in the hover card
    url: f.type.startsWith('image/') ? URL.createObjectURL(f) : undefined,
  }));
  prompt.attachments = [...current, ...added];
});
```

The drop zone and the paperclip stack: files from either path land in the same staged list and ride along on the next `kai-submit`.

## Next steps

- **[`kai-prompt-input` reference](/components/prompt-input/)** — `attachments` property, the built-in paperclip, `kai-submit` detail, `loading`, `stoppable`.
- **[`kai-attachments` reference](/components/attachments/)** — standalone display of `AttachmentData[]` with `variant`, `hoverCard`, and `removable`.
- **[`kai-file-upload` reference](/components/file-upload/)** — `accept`, `multiple`, `kai-files-added`.
- **[`kai-message` reference](/components/message/)** — full `ChatMessage` shape, including `file` parts.
- **[Drop-in chat](/examples/drop-in-chat/)** — the complete `kai-submit` streaming loop with `<kai-chat>`.
