<!-- AUTO-GENERATED by scripts/gen-llms.mjs — do not edit by hand. Run `npm run build`. -->
# @kitn.ai/chat — Full Reference

# @kitn.ai/chat

> Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. 41 `kitn-*` custom elements: streaming responses, markdown + code rendering, reasoning/tool panels, attachments, conversation sidebar, voice input. Zero framework dependency for consumers; the SolidJS runtime it is authored in is bundled in, so the host needs nothing.

## Install

```bash
npm install @kitn.ai/chat
# SolidJS consumers also need the peer dep:
npm install solid-js
```

## #1 rule: array/object data goes on JS PROPERTIES, not HTML attributes

This is the single most common mistake. Arrays and objects (`messages`, `models`, `context`, `suggestions`, `slashCommands`, …) MUST be assigned as JavaScript properties on the element. They CANNOT be passed as HTML attributes — an HTML attribute is always a string and will be ignored or mis-parsed.

```js
const chat = document.querySelector('kc-chat');
chat.messages = [{ id: '1', role: 'assistant', content: 'Hi!' }]; // ✅ property
```
```html
<kc-chat messages="[...]"></kc-chat>  <!-- ❌ never works -->
```

Only scalar values (string/number/boolean) work as attributes (e.g. `placeholder`, `loading`, `theme`).

## Two layers

**Layer 1 — batteries-included web components** (`import '@kitn.ai/chat/elements'`):
Drop an element into any framework (React, Vue, plain HTML). Data in via JS properties; interactions out via non-bubbling CustomEvents.

- `<kc-chat>` — full chat UI (message list + prompt input). The primary starting point.
- `<kc-conversations>` — sidebar conversation browser with group support.
- `<kc-prompt-input>` — standalone composer with send button.

**Layer 2 — composable primitives** (`import { … } from '@kitn.ai/chat'`):
All 41 elements are also exported individually. Use them for custom layouts or features `<kc-chat>` does not expose (ChainOfThought, FeedbackBar, ThinkingBar, VoiceInput, …). Your bundler tree-shakes the rest.

## Key rules for the web components

1. **Array/object data = JS properties** (see above). Scalars may be attributes.
2. **Events are non-bubbling `CustomEvent`s** — listen directly on the element:
   `chat.addEventListener('submit', (e) => console.log(e.detail.value))`
3. **`theme` attribute** (`'light' | 'dark' | 'auto'`) works on every element. Default `auto` follows `prefers-color-scheme`.
4. **Theming via CSS custom properties** — override `--kc-color-*` tokens on `:root`; they pierce Shadow DOM.

## ChatMessage schema (required for `<kc-chat>`)

```ts
interface ChatMessage {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  reasoning?: { text: string; label?: string };
  tools?: ToolPart[];
  attachments?: AttachmentData[];
  actions?: ('copy' | 'like' | 'dislike' | 'regenerate' | 'edit')[];
}
```

## Framework wiring

**Plain HTML / CDN**
```html
<script type="module" src="https://unpkg.com/@kitn.ai/chat/elements"></script>
<kc-chat style="display:block;height:100vh"></kc-chat>
<script type="module">
  const chat = document.querySelector('kc-chat');
  chat.messages = [];
</script>
```

**React** — typed wrappers auto-set properties and expose `on<Event>` props:
```tsx
import { Chat } from '@kitn.ai/chat/react';
<Chat messages={messages} onSubmit={(e) => send(e.detail.value)} />
```

**Vue** — use the element directly; pass arrays via `.prop`:
```vue
<kc-chat :messages.prop="messages" @submit="send" />
```

## Theming

```css
:root {
  --kc-color-background: #0f0f0f;
  --kc-color-primary: #7c3aed;
  --kc-color-muted: #1e1e1e;
}
```

For plain HTML/CDN: `<link rel="stylesheet" href="…/@kitn.ai/chat/theme.tokens.css">`.
For Tailwind builds: `@import "@kitn.ai/chat/theme.css"` in your CSS.

## Docs

- Full element reference (all 41 elements, every prop/event): ./llms-full.txt — https://kitn.dev/llms-full.txt
- Machine-readable Custom Elements Manifest: https://unpkg.com/@kitn.ai/chat/dist/custom-elements.json
- Working examples: https://github.com/kitn-ai/chat/tree/main/examples
- Storybook: https://storybook.kitn.dev
- Repository: https://github.com/kitn-ai/chat

---

## How to build a chat app in 5 steps

### 1 — Install
```bash
npm install @kitn.ai/chat
```

### 2 — Pick your layer
Drop-in: use `<kc-chat>` for a full chat UI in one tag (`import '@kitn.ai/chat/elements'`).
Composable: combine `<kc-message>`, `<kc-prompt-input>`, `<kc-reasoning>`, … in your own markup.

### 3 — Handle `submit` and stream
```js
import '@kitn.ai/chat/elements';
const chat = document.querySelector('kc-chat');
chat.messages = [];

chat.addEventListener('submit', async (e) => {
  const userText = e.detail.value;

  // Append the user message (new array — see streaming note)
  const history = [...chat.messages, { id: crypto.randomUUID(), role: 'user', content: userText }];
  chat.messages = history;
  chat.loading = true;

  // Add an empty assistant placeholder to stream into
  const aid = crypto.randomUUID();
  chat.messages = [...history, { id: aid, role: 'assistant', content: '' }];

  let answer = '';
  for await (const token of streamFromYourAPI(history)) {
    answer += token;
    chat.messages = chat.messages.map((m) => (m.id === aid ? { ...m, content: answer } : m));
  }
  chat.loading = false;
});
```

### 4 — Wire optional features
- Reasoning: add `reasoning: { text: '…' }` to an assistant message.
- Tool calls: add `tools: [{ type: 'search', state: 'output-available', input: {…}, output: {…} }]`.
- Model switcher: `chat.models = [{ id: 'gpt-4o', name: 'GPT-4o' }]; chat.currentModel = 'gpt-4o';` — listen for `modelchange`.
- Token meter: `chat.context = { usedTokens: 1200, maxTokens: 128000 };`.
- History sidebar: add `<kc-conversations>`; listen for `select` and `newchat`.

### 5 — Theme
Override `--kc-color-*` tokens on `:root` (they pierce Shadow DOM).

---

## Streaming recipe (critical)

To update messages while streaming, **reassign a NEW array containing a NEW message object** on every chunk. Mutating an existing message object in place will NOT trigger a re-render:

```js
// ✅ re-renders
chat.messages = chat.messages.map((m) => (m.id === id ? { ...m, content: next } : m));

// ❌ does NOT re-render
chat.messages[i].content = next;
```

The same rule applies to every array/object property (`models`, `context`, `suggestions`, …): replace, don't mutate.

---

## Element reference (41 elements, generated from custom-elements.json)

Every element also accepts the `theme` attribute. Array/object properties are marked with a `—` attribute: they must be set as JS properties.

### `kc-artifact` / `Artifact`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `src` | `src` | `undefined \| string` | URL the preview iframe frames. Consumer-controlled. |
| `files` | — | `{ path: string; url?: undefined \| string; code?: undefined \| string; language?: undefined \| string; type?: undefined \| "html" \| "pdf" \| "image" \| "other" }[]` | Files for the Code tab tree + each file's preview `url`. Set as a JS property (array). |
| `tab` | `tab` | `undefined \| "preview" \| "code"` | Active tab: `preview` (default) or `code`. |
| `activeFile` | `active-file` | `undefined \| string` | Selected file path — syncs the tree highlight, Code source, and preview. |
| `sandbox` | `sandbox` | `undefined \| string` | iframe `sandbox` override. Secure default `allow-scripts allow-forms` (NOT `allow-same-origin`). |
| `iframeTitle` | `iframe-title` | `undefined \| string` | Accessible title for the preview iframe. |
| `maximized` | `maximized` | `undefined \| false \| true` | Reflects the artifact's own maximized view-state (usually driven by the protocol). |
| `expandable` | `expandable` | `undefined \| false \| true` | Show the expand-to-fill button (OPT-IN). |
| `openInTab` | `open-in-tab` | `undefined \| false \| true` | Show the open-in-new-tab button (OPT-IN). |
| `noNav` | `no-nav` | `undefined \| false \| true` | Hide back/forward. |
| `noReload` | `no-reload` | `undefined \| false \| true` | Hide reload. |
| `noHome` | `no-home` | `undefined \| false \| true` | Hide home. |
| `noPathField` | `no-path-field` | `undefined \| false \| true` | Hide the address field. |
| `noTabs` | `no-tabs` | `undefined \| false \| true` | Hide the Preview\|Code toggle. |
| `standalone` | `standalone` | `undefined \| false \| true` | Standalone chrome: rounded corners + border (else square, borderless in-panel). |
| `readonlyPath` | `readonly-path` | `undefined \| false \| true` | Show the address but make it read-only (visible, nav-tracking, non-editable). |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `fileselect` | `CustomEvent<{ path: string }>` | Fired when a file is selected. `detail.path`. |
| `maximizechange` | `CustomEvent<{ maximized: false \| true }>` | Artifact's own maximize button toggled (consumer-observable; non-bubbling). |
| `navigate` | `CustomEvent<{ url: string }>` | Fired when the preview navigates. `detail.url` = the new location. |
| `tabchange` | `CustomEvent<{ tab: "preview" \| "code" }>` | Fired when the Preview\|Code tab changes. `detail.tab`. |

---

### `kc-attachments` / `Attachments`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `items` | — | `{ id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]` | The attachments to render. Set as a JS property (array). |
| `variant` | `variant` | `undefined \| "grid" \| "inline" \| "list"` | Layout: `grid` = visual tiles, `inline` = icon + label chips, `list` = rows. |
| `hoverCard` | `hover-card` | `undefined \| false \| true` | Wrap each item in a hover card that previews its details. |
| `removable` | `removable` | `undefined \| false \| true` | Show a remove button per item; clicking it fires a `remove` event. |
| `showMediaType` | `show-media-type` | `undefined \| false \| true` | Also show the media type beneath the filename (non-grid variants). |
| `emptyText` | `empty-text` | `undefined \| string` | Text shown when `items` is empty. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `remove` | `CustomEvent<{ id: string }>` | A remove button was clicked. |

---

### `kc-card` / `Card`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `heading` | `heading` | `undefined \| string` | Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. |
| `description` | `description` | `undefined \| string` | Supporting text under the heading. Attribute: `description`. |
| `errorMessage` | `error-message` | `undefined \| string` | When set, the card renders its inline error state instead of the body. Attribute: `error-message`. |
| `dense` | `dense` | `undefined \| false \| true` | Compact spacing for dense lists. Attribute: `dense`. |

_No events._

---

### `kc-cards` / `Cards`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `cards` | — | `undefined \| { type: string; id: string; data: unknown; title?: undefined \| string; resolution?: undefined \| { kind: "action"; action: string; payload?: unknown; at?: undefined \| string } \| { kind: "submit"; data: unknown; at?: undefined \| string } }[]` | The stream of card envelopes to render. Set as a JS PROPERTY: `el.cards = [...]`. |
| `types` | — | `undefined \| Record<string, string>` | Optional type→tag overrides/additions (merged over the built-ins). Property: `el.types`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type. |
| `policy` | — | `undefined \| { onSubmit?: undefined \| (cardId: string, data: unknown) => void; onAction?: undefined \| (cardId: string, action: string, payload?: unknown) => void; onSendPrompt?: undefined \| (text: string, opts: { mode: "compose" \| "send"; context?: unknown; }) => void; onOpen?: undefined \| (url: string, target: "tab" \| "artifact") => void; onState?: undefined \| (cardId: string, patch: unknown) => void; onDismiss?: undefined \| (cardId: string) => void; onError?: undefined \| (cardId: string, message: string) => void; maxSendPromptMode?: undefined \| "compose" \| "send" }` | Optional CardPolicy handling child events. Property: `el.policy`. |

_No events._

---

### `kc-chain-of-thought` / `ChainOfThought`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `steps` | — | `{ label: string; content?: undefined \| string }[]` | The reasoning steps. Set as a JS property. Compound sub-parts collapse to this one data model (Route 1). |

_No events._

---

### `kc-chat` / `Chat`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `messages` | — | `{ id: string; role: "user" \| "assistant"; content: string; reasoning?: undefined \| { text: string; label?: undefined \| string }; tools?: undefined \| { type: string; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string }[]; attachments?: undefined \| { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]; actions?: undefined \| ("copy" \| "like" \| "dislike" \| "regenerate" \| "edit" \| { id: string; label: string; icon?: undefined \| string })[]; avatar?: undefined \| { src?: undefined \| string; fallback?: undefined \| string; alt?: undefined \| string } }[]` | The full message thread to render, newest last. Each entry carries its role, content, and optional reasoning/tools/attachments/actions. Set as a JS property (`el.messages = [...]`). |
| `value` | `value` | `undefined \| string` | Controlled value of the input. When set, the host owns the input text and must update it on `valuechange`; leave unset for uncontrolled behavior. |
| `placeholder` | `placeholder` | `undefined \| string` | Placeholder text shown in the empty input. |
| `loading` | `loading` | `undefined \| false \| true` | When true, shows the loading/streaming state and disables submit (use while awaiting the assistant's reply). |
| `suggestions` | — | `undefined \| string[]` | Starter prompts shown above the input when the thread is empty. Clicking one follows `suggestionMode`. Set as a JS property. |
| `suggestionMode` | `suggestion-mode` | `undefined \| "submit" \| "fill"` | What clicking a suggestion does: `'submit'` (default) sends it immediately as if typed and submitted; `'fill'` just places it in the input. |
| `proseSize` | `prose-size` | `undefined \| "xs" \| "sm" \| "base" \| "lg"` | Body/prose font scale for rendered markdown (`'xs' \| 'sm' \| 'base' \| 'lg'`). Defaults to `'sm'`. |
| `codeTheme` | `code-theme` | `undefined \| string` | Shiki theme name for syntax-highlighted code blocks (e.g. `'github-dark-dimmed'`). |
| `codeHighlight` | `code-highlight` | `undefined \| false \| true` | Enable Shiki syntax highlighting in code blocks. Turn off to render plain `<pre>` blocks (lighter, no highlighter load). Default true. |
| `chatTitle` | `chat-title` | `undefined \| string` | Optional header title shown on the left of the header. |
| `models` | — | `undefined \| { id: string; name: string; provider?: undefined \| string }[]` | Optional model list. When set (>1 model) a ModelSwitcher is shown in the header and a `modelchange` event fires on selection. |
| `currentModel` | `current-model` | `undefined \| string` | The currently selected model id (pairs with `models`). |
| `context` | — | `undefined \| { usedTokens: number; maxTokens: number; inputTokens?: undefined \| number; outputTokens?: undefined \| number; estimatedCost?: undefined \| number }` | Optional context-window token usage. When set, a Context token meter is shown in the header. |
| `scrollButton` | `scroll-button` | `undefined \| false \| true` | Show the scroll-to-bottom button inside the scroll area. Default true. |
| `search` | `search` | `undefined \| false \| true` | Show a Search (Globe) button in the input toolbar; fires a `search` event. |
| `voice` | `voice` | `undefined \| false \| true` | Show a Voice (Mic) button in the input toolbar; fires a `voice` event. |
| `slashCommands` | — | `undefined \| { id: string; label: string; description?: undefined \| string; category?: undefined \| string }[]` | Slash commands — when set, typing `/` in the input opens the command palette and fires `slashselect`. Set as a JS property. |
| `slashActiveIds` | — | `undefined \| string[]` | Command ids to highlight as active in the palette. |
| `slashCompact` | `slash-compact` | `undefined \| false \| true` | Single-line palette rows. |
| `actionsReveal` | `actions-reveal` | `undefined \| "always" \| "hover"` | Whether each message's action bar is always visible (`'always'`, default) or only revealed on hover of that message row (`'hover'`). |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `messageaction` | `CustomEvent<{ messageId: string; action: string }>` | An action button on a message was clicked. `action` is the built-in name or custom id. |
| `modelchange` | `CustomEvent<{ modelId: string }>` | The header model switcher changed. |
| `search` | `CustomEvent<Record<string, never>>` | The Search button was clicked. |
| `slashselect` | `CustomEvent<{ command: { id: string; label: string; description?: undefined \| string; category?: undefined \| string } }>` | A slash command was chosen from the palette. |
| `submit` | `CustomEvent<{ value: string; attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | User submitted a message. |
| `suggestionclick` | `CustomEvent<{ value: string }>` | A suggestion chip was clicked (only in `suggestion-mode="fill"`). |
| `valuechange` | `CustomEvent<{ value: string }>` | Fired on every input change. |
| `voice` | `CustomEvent<Record<string, never>>` | The Mic / voice button was clicked. |

---

### `kc-checkpoint` / `Checkpoint`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `label` | `label` | `undefined \| string` | Optional text beside the icon. |
| `tooltip` | `tooltip` | `undefined \| string` | Tooltip on hover. |
| `variant` | `variant` | `undefined \| "ghost" \| "default" \| "outline"` | Visual button style. |
| `size` | `size` | `undefined \| "sm" \| "lg" \| "md" \| "icon" \| "icon-sm"` | Button size (use an `icon*` size for an icon-only checkpoint). |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `select` | `CustomEvent` | The checkpoint was clicked. |

---

### `kc-choice` / `Choice`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `data` | — | `undefined \| Record<string, unknown>` | The choice definition (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { prompt, options:[…], allowOther?, submitLabel? }`. Import `ChoiceCardData` from `@kitn.ai/chat` for the full shape. |
| `cardId` | `card-id` | `undefined \| string` | Stable card id correlating every emitted CardEvent. Attribute: `card-id`. |
| `heading` | `heading` | `undefined \| string` | Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. |
| `resolution` | — | `undefined \| Record<string, unknown>` | Set when the user resolved this card; renders the read-only view. Property: `el.resolution = { kind:'action', action:'…' }`. |

_No events._

---

### `kc-code-block` / `CodeBlock`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `code` | `code` | `string` | The source code to render. |
| `language` | `language` | `undefined \| string` | Language grammar (e.g. `js`, `python`). Defaults to `tsx`. |
| `codeTheme` | `code-theme` | `undefined \| string` | Shiki theme name. |
| `codeHighlight` | `code-highlight` | `undefined \| false \| true` | Disable syntax highlighting (renders plain text, no Shiki). |
| `proseSize` | `prose-size` | `undefined \| "xs" \| "sm" \| "base" \| "lg"` | Code text sizing. |

_No events._

---

### `kc-confirm` / `Confirm`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `data` | — | `undefined \| Record<string, unknown>` | The confirm definition (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { body, tone, actions:[…] }`. Import `ConfirmCardData` from `@kitn.ai/chat` for the full shape. |
| `cardId` | `card-id` | `undefined \| string` | Stable card id correlating every emitted CardEvent. Attribute: `card-id`. |
| `heading` | `heading` | `undefined \| string` | Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. |
| `autofocus` | `autofocus` | `undefined \| false \| true` | Focus the default action on mount (off by default — no focus-stealing). Attribute: `autofocus`. |
| `resolution` | — | `undefined \| Record<string, unknown>` | Set when the user resolved this card; renders the read-only view. Property: `el.resolution = { kind:'action', action:'…' }`. |

_No events._

---

### `kc-context` / `Context`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `context` | — | `undefined \| { usedTokens: number; maxTokens: number; inputTokens?: undefined \| number; outputTokens?: undefined \| number; reasoningTokens?: undefined \| number; cacheTokens?: undefined \| number; estimatedCost?: undefined \| number }` | Token-usage data. Set as a JS property. |

_No events._

---

### `kc-conversations` / `Conversations`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `groups` | — | `{ id: string; userId?: undefined \| string; teamId?: undefined \| string; name: string; sortOrder: number; createdAt: string }[]` | Pre-bucketed conversation groups (e.g. "Today", "Yesterday"), each with its own conversations. Use this when you want to control the grouping/headers yourself; otherwise pass a flat `conversations` array. Set as a JS property. |
| `conversations` | — | `{ id: string; title: string; groupId?: undefined \| string; scope: { type: "document" \| "collection"; documentId?: undefined \| string; filters?: undefined \| { tags?: undefined \| string[]; authors?: undefined \| string[]; contentType?: undefined \| "transcript" \| "markdown"; dateRange?: undefined \| { from: string; to: string } } }; messageCount: number; lastMessageAt: string; updatedAt: string }[]` | A flat list of conversation summaries; the component buckets them by recency for you. Ignored when `groups` is provided. Set as a JS property. |
| `activeId` | `active-id` | `undefined \| string` | The id of the currently-open conversation, highlighted in the list. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `conversationselect` | `CustomEvent<{ id: string }>` | A conversation was selected. |
| `newchat` | `CustomEvent<Record<string, never>>` | The "New chat" button was clicked. |
| `togglesidebar` | `CustomEvent<Record<string, never>>` | The sidebar toggle was clicked. |

---

### `kc-embed` / `Embed`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `cardId` | `card-id` | `undefined \| string` | Stable card id correlating every emitted event. Set as an attribute or property. |
| `data` | — | `undefined \| { provider: "youtube" \| "vimeo" \| "generic"; id?: undefined \| string; url?: undefined \| string; title?: undefined \| string; poster?: undefined \| string; start?: undefined \| number; aspectRatio?: undefined \| "16:9" \| "4:3" \| "1:1" \| "9:16" }` | The embed payload (provider + id/url + options). Set as a JS **property** (object). |

_No events._

---

### `kc-empty` / `Empty`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `emptyTitle` | `empty-title` | `undefined \| string` | Title text. Attribute: `empty-title` (`title` is a global HTML attribute). |
| `description` | `description` | `undefined \| string` | Description text. |

_No events._

---

### `kc-feedback-bar` / `FeedbackBar`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `barTitle` | `bar-title` | `undefined \| string` | The banner label (e.g. "Was this helpful?"). Attribute: `bar-title` (`title` is avoided — it's a global HTML attribute). |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `close` | `CustomEvent` | The user dismissed the banner. |
| `feedback` | `CustomEvent<{ value: "helpful" \| "not-helpful" }>` | The user rated the response. `value` is `'helpful'` or `'not-helpful'`. |

---

### `kc-file-tree` / `FileTree`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `files` | — | `{ path: string; url?: undefined \| string; code?: undefined \| string; language?: undefined \| string; type?: undefined \| "html" \| "pdf" \| "image" \| "other" }[]` | The files to render. Set as a JS property (array of `{ path, url?, code?, language?, type? }`). |
| `activeFile` | `active-file` | `undefined \| string` | Selected file path — highlighted in the tree. |
| `defaultExpanded` | — | `undefined \| string[]` | Folder paths expanded initially. Omit to start with all folders open. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `select` | `CustomEvent<{ path: string }>` | Fired when a file is selected. `detail.path` = the file's path. |

---

### `kc-file-upload` / `FileUpload`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `multiple` | `multiple` | `undefined \| false \| true` | Allow selecting multiple files (default true). |
| `accept` | `accept` | `undefined \| string` | `accept` attribute for the file picker (e.g. `image/*`). |
| `disabled` | `disabled` | `undefined \| false \| true` | Disable the dropzone — no clicking, no drag-and-drop. |
| `label` | `label` | `undefined \| string` | Default dropzone label (overridable via the default slot). |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `filesadded` | `CustomEvent<{ files: File[] }>` | Files were picked or dropped. |

---

### `kc-form` / `Form`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `data` | — | `undefined \| Record<string, unknown>` | The form definition — a JSON Schema (`type:'object'`) + `x-kc-*` UI hints (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { type:'object', properties:{…} }`. Import the `FormDefinition` type from `@kitn.ai/chat` for the full shape (it is self-referential, so the element types it loosely). |
| `cardId` | `card-id` | `undefined \| string` | Stable card id correlating every emitted CardEvent. Attribute: `card-id`. |
| `heading` | `heading` | `undefined \| string` | Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. |
| `resolution` | — | `undefined \| Record<string, unknown>` | Set when the user resolved this card; renders the read-only view. Property: `el.resolution = { kind:'submit', data:{…} }`. |

_No events._

---

### `kc-image` / `Image`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `base64` | `base64` | `undefined \| string` | Base64-encoded image data (pair with `media-type`). |
| `bytes` | — | `undefined \| Uint8Array<ArrayBufferLike>` | Raw image bytes (set as a JS property). |
| `alt` | `alt` | `undefined \| string` | Alt text. |
| `mediaType` | `media-type` | `undefined \| string` | MIME type (default `image/png`). |

_No events._

---

### `kc-link-preview` / `LinkPreview`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `cardId` | `card-id` | `undefined \| string` | Stable card id correlating every emitted event. Set as an attribute or property. |
| `data` | — | `undefined \| { url: string; title?: undefined \| string; description?: undefined \| string; image?: undefined \| string; imageAlt?: undefined \| string; favicon?: undefined \| string; domain?: undefined \| string; siteName?: undefined \| string }` | The link payload (OG metadata). Set as a JS **property** (object). |

_No events._

---

### `kc-loader` / `Loader`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `variant` | `variant` | `undefined \| "circular" \| "classic" \| "pulse" \| "pulse-dot" \| "dots" \| "typing" \| "wave" \| "bars" \| "terminal" \| "text-blink" \| "text-shimmer" \| "loading-dots"` | The animation style: `'circular' \| 'classic' \| 'pulse' \| 'pulse-dot' \| 'dots' \| 'typing' \| 'wave' \| 'bars' \| 'terminal' \| 'text-blink' \| 'text-shimmer' \| 'loading-dots'`. Defaults to `'circular'`. |
| `size` | `size` | `undefined \| "sm" \| "lg" \| "md"` | Loader size: `'sm' \| 'md' \| 'lg'`. Defaults to `'md'`. |
| `text` | `text` | `undefined \| string` | Label for the text-based variants. |

_No events._

---

### `kc-markdown` / `Markdown`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `content` | `content` | `string` | The markdown source to render. |
| `proseSize` | `prose-size` | `undefined \| "xs" \| "sm" \| "base" \| "lg"` | Text/markdown sizing. |
| `codeTheme` | `code-theme` | `undefined \| string` | Shiki theme for fenced code blocks. |
| `codeHighlight` | `code-highlight` | `undefined \| false \| true` | Disable syntax highlighting (no Shiki loads). |

_No events._

---

### `kc-message` / `Message`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `message` | — | `undefined \| { id: string; role: "user" \| "assistant"; content: string; reasoning?: undefined \| { text: string; label?: undefined \| string }; tools?: undefined \| { type: string; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string }[]; attachments?: undefined \| { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]; actions?: undefined \| ("copy" \| "like" \| "dislike" \| "regenerate" \| "edit" \| { id: string; label: string; icon?: undefined \| string })[]; avatar?: undefined \| { src?: undefined \| string; fallback?: undefined \| string; alt?: undefined \| string } }` | The full message object. Set as a JS property. |
| `role` | `role` | `undefined \| "user" \| "assistant"` | Convenience for simple cases when not passing a `message` object. |
| `content` | `content` | `undefined \| string` | Convenience content (used when `message` is not set). |
| `markdown` | `markdown` | `undefined \| false \| true` | Force markdown on/off. Defaults to on for assistant, off for user. |
| `proseSize` | `prose-size` | `undefined \| "xs" \| "sm" \| "base" \| "lg"` | Text/markdown sizing for the message body. |
| `codeTheme` | `code-theme` | `undefined \| string` | Shiki theme name used for fenced code blocks in the content. |
| `codeHighlight` | `code-highlight` | `undefined \| false \| true` | Disable syntax highlighting for code blocks (no Shiki loads). |
| `actionsReveal` | `actions-reveal` | `undefined \| "always" \| "hover"` | Whether the action bar is always visible (`'always'`, default) or only revealed on hover of the message row (`'hover'`). |
| `avatarSrc` | `avatar-src` | `undefined \| string` | Convenience avatar image URL (used when `message.avatar` is not set). |
| `avatarFallback` | `avatar-fallback` | `undefined \| string` | Convenience avatar fallback text (used when `message.avatar` is not set). |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `messageaction` | `CustomEvent<{ messageId: string; action: string }>` | An action button was clicked. `action` is the built-in name or custom id. |

---

### `kc-model-switcher` / `ModelSwitcher`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `models` | — | `{ id: string; name: string; provider?: undefined \| string }[]` | The selectable models. Set as a JS property (array). |
| `currentModel` | `current-model` | `undefined \| string` | The currently-selected model id. Defaults to the first model. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `modelchange` | `CustomEvent<{ modelId: string }>` | A model was selected. |

---

### `kc-prompt-input` / `PromptInput`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `value` | `value` | `undefined \| string` | Controlled value of the input. When set, the host owns the text and must update it on `valuechange`; leave unset for uncontrolled behavior. |
| `placeholder` | `placeholder` | `undefined \| string` | Placeholder text shown in the empty input. |
| `disabled` | `disabled` | `undefined \| false \| true` | Disable the input and submit button entirely (non-interactive). |
| `loading` | `loading` | `undefined \| false \| true` | Show the loading/streaming state and block submit (use while awaiting a reply). |
| `suggestions` | — | `undefined \| string[]` | Starter prompts shown above the input. Clicking one follows `suggestionMode`. Set as a JS property. |
| `suggestionMode` | `suggestion-mode` | `undefined \| "submit" \| "fill"` | What clicking a suggestion does: `'submit'` (default) sends it immediately as if typed and submitted; `'fill'` just places it in the input. |
| `slashCommands` | — | `undefined \| { id: string; label: string; description?: undefined \| string; category?: undefined \| string }[]` | Slash commands — when set, typing `/` opens the command palette. Set as a JS property. |
| `slashActiveIds` | — | `undefined \| string[]` | Command ids to highlight as active. |
| `slashCompact` | `slash-compact` | `undefined \| false \| true` | Single-line palette rows. |
| `search` | `search` | `undefined \| false \| true` | Show a Search (Globe) button in the left toolbar; clicking it fires a `search` event. |
| `voice` | `voice` | `undefined \| false \| true` | Show a Voice (Mic) button in the left toolbar; clicking it fires a `voice` event. |
| `attachments` | — | `undefined \| { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]` | Attachments to seed the input with (so a consumer can pre-populate staged files without an upload). Set as a JS property; the element then manages its own attachment state from there (add via the paperclip, remove per chip). |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `search` | `CustomEvent<Record<string, never>>` | The Search (Globe) toolbar button was clicked. |
| `slashselect` | `CustomEvent<{ command: { id: string; label: string; description?: undefined \| string; category?: undefined \| string } }>` | A slash command was chosen from the palette. |
| `submit` | `CustomEvent<{ value: string; attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | The user submitted the prompt (Enter or send button) with its attachments. |
| `suggestionclick` | `CustomEvent<{ value: string }>` | A suggestion was clicked while `suggestion-mode="fill"`. |
| `valuechange` | `CustomEvent<{ value: string }>` | The input text changed (fires on every keystroke). |
| `voice` | `CustomEvent<Record<string, never>>` | The Voice (Mic) toolbar button was clicked. |

---

### `kc-reasoning` / `Reasoning`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `text` | `text` | `string` | The reasoning text to display. |
| `label` | `label` | `undefined \| string` | Trigger label. |
| `open` | `open` | `undefined \| false \| true` | Controlled open state — set as a property (`el.open = true`). Omit for uncontrolled (the trigger toggles it). |
| `streaming` | `streaming` | `undefined \| false \| true` | While true, auto-expands (and re-collapses when it flips false). |
| `markdown` | `markdown` | `undefined \| false \| true` | Render `text` as markdown. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `openchange` | `CustomEvent<{ open: false \| true }>` | Open state changed (via the trigger or streaming auto-open). |

---

### `kc-remote` / `Remote`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `src` | `src` | `undefined \| string` | The remote card URL. Attribute: `src`. |
| `providerOrigin` | `provider-origin` | `undefined \| string` | Exact provider origin (https: or http://localhost for dev). Attribute: `provider-origin`. |
| `envelope` | — | `undefined \| Record<string, unknown>` | The card envelope to render. JS property only. |
| `policy` | — | `undefined \| Record<string, unknown>` | Optional routing policy. JS property only. |

_No events._

---

### `kc-resizable` / `Resizable`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `orientation` | `orientation` | `undefined \| "horizontal" \| "vertical"` | Layout axis: `horizontal` (row, default) or `vertical` (column). |
| `maximizedIndex` | — | `undefined \| null \| number` | Which item index is maximized (null = none). Declarative source of truth. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `change` | `CustomEvent<{ sizes: number[] }>` | Fired on drag-end / keyboard resize / visibility change. `detail.sizes` = panel sizes in percent. |
| `maximizechange` | `CustomEvent<{ maximized: false \| true; index: null \| number }>` | Observe layout maximize state. |

---

### `kc-resizable-item` / `ResizableItem`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `size` | `size` | `undefined \| string` | Initial main-axis size: `"280px"` (fixed) or `"25%"`/`25` (percent). Omitted → flexible. |
| `min` | `min` | `undefined \| string` | Minimum size during resize (px or %). |
| `max` | `max` | `undefined \| string` | Maximum size during resize (px or %). |
| `locked` | `locked` | `undefined \| false \| true` | Fix this panel's size; adjacent dividers become non-draggable. |
| `hidden` | `hidden` | `undefined \| false \| true` | Hide this panel; its divider is dropped and the rest reflow. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `change` | `CustomEvent<unknown>` |  |
| `maximizechange` | `CustomEvent<unknown>` |  |

---

### `kc-response-stream` / `ResponseStream`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `text` | — | `undefined \| string \| AsyncIterable<string>` | Text to stream. A string, or an `AsyncIterable<string>` (set as a JS property — async iterables can't be HTML attributes). |
| `mode` | `mode` | `undefined \| "typewriter" \| "fade"` | Reveal animation. |
| `speed` | `speed` | `undefined \| number` | Characters/segments per tick. |
| `as` | `as` | `undefined \| string` | Element tag to render as. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `complete` | `CustomEvent` | Streaming finished. |

---

### `kc-scope-picker` / `ScopePicker`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `availableAuthors` | — | `string[]` | Authors to offer as scope filters. Set as a JS property. |
| `availableTags` | — | `string[]` | Tags to offer as scope filters. Set as a JS property. |
| `currentLabel` | `current-label` | `undefined \| string` | The label shown on the trigger for the active scope. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `scopechange` | `CustomEvent<{ filters: undefined \| { tags?: undefined \| string[]; authors?: undefined \| string[]; contentType?: undefined \| "transcript" \| "markdown"; dateRange?: undefined \| { from: string; to: string } } }>` | A scope was chosen (`undefined` filters = "All Content"). |

---

### `kc-skills` / `Skills`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `skills` | — | `{ id: string; name: string }[]` | The active skills to badge. Set as a JS property. |

_No events._

---

### `kc-source` / `Source`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `href` | `href` | `undefined \| string` | The URL this citation links to (the domain also seeds the default label/favicon). |
| `label` | `label` | `undefined \| string` | Trigger label (defaults to the domain). |
| `headline` | `headline` | `undefined \| string` | Hover-card headline. Attribute: `headline` (`title` is avoided — it's a global HTML attribute that reflects in a CE constructor and breaks it). |
| `description` | `description` | `undefined \| string` | Hover-card body text describing the source. |
| `showFavicon` | `show-favicon` | `undefined \| false \| true` | Show the source's favicon next to the trigger label. |

_No events._

---

### `kc-sources` / `Sources`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `sources` | — | `{ href: string; title?: undefined \| string; description?: undefined \| string; label?: undefined \| string; showFavicon?: undefined \| false \| true }[]` | The sources to render. Set as a JS property. |
| `showFavicon` | `show-favicon` | `undefined \| false \| true` | Show favicons on all items (per-item `showFavicon` overrides). |

_No events._

---

### `kc-suggestions` / `Suggestions`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `suggestions` | — | `(string \| { label: string; value?: undefined \| string })[]` | The suggestions. Strings, or `{ label, value }` when the displayed text and the emitted value differ. Set as a JS property. |
| `variant` | `variant` | `undefined \| "ghost" \| "default" \| "outline"` | Chip style: `'outline'` (default), `'ghost'`, or `'default'` (filled). |
| `size` | `size` | `undefined \| "sm" \| "lg" \| "md" \| "icon" \| "icon-sm"` | Size preset for each chip. Defaults to the pill default (`'lg'`); pass `'sm'` for smaller pills (or `'md'`). |
| `block` | `block` | `undefined \| false \| true` | Full-width left-aligned rows instead of pills. |
| `highlight` | `highlight` | `undefined \| string` | Substring to highlight within each suggestion. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `select` | `CustomEvent<{ value: string }>` | A suggestion was clicked. |

---

### `kc-tasks` / `Tasks`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `data` | — | `undefined \| Record<string, unknown>` | The tasks definition (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { tasks:[…], selectAll, confirmLabel, … }`. Import `TasksCardData` from `@kitn.ai/chat` for the full shape. |
| `cardId` | `card-id` | `undefined \| string` | Stable card id correlating every emitted CardEvent. Attribute: `card-id`. |
| `heading` | `heading` | `undefined \| string` | Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. |
| `resolution` | — | `undefined \| Record<string, unknown>` | Set when the user resolved this card; renders the read-only view. Property: `el.resolution = { kind:'submit', data:{ selected:[…] } }`. |

_No events._

---

### `kc-text-shimmer` / `TextShimmer`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `text` | `text` | `undefined \| string` | The text to shimmer. |
| `as` | `as` | `undefined \| string` | Element tag to render as (default `span`). |
| `duration` | `duration` | `undefined \| number` | Animation duration in seconds. |
| `spread` | `spread` | `undefined \| number` | Gradient spread (5–45). |

_No events._

---

### `kc-thinking-bar` / `ThinkingBar`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `text` | `text` | `undefined \| string` | The shimmering label, e.g. "Thinking…". |
| `stoppable` | `stoppable` | `undefined \| false \| true` | When true, show a "stop" affordance that fires a `stop` event. |
| `stopLabel` | `stop-label` | `undefined \| string` | Label for the stop affordance. |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `stop` | `CustomEvent` | The "stop / answer now" affordance was clicked. |

---

### `kc-tool` / `Tool`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `tool` | — | `undefined \| { type: string; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string }` | The tool-call to display. Set as a JS property. |
| `open` | `open` | `undefined \| false \| true` | Start expanded. |

_No events._

---

### `kc-voice-input` / `VoiceInput`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `transcribe` | — | `undefined \| (audio: Blob) => Promise<string>` | Transcriber the host supplies — records audio, returns the text. This is a **function-valued property** (`el.transcribe = async blob => '...'`) because a value-returning callback can't be modelled as a fire-and-forget event. |
| `disabled` | `disabled` | `undefined \| false \| true` | Disable the mic button (non-interactive). |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `audiocaptured` | `CustomEvent<{ blob: Blob }>` | Raw audio captured (before transcription) — for hosts that prefer to handle transcription themselves instead of via the `transcribe` property. |
| `transcription` | `CustomEvent<{ text: string }>` | Transcription completed (the `transcribe` property resolved). |

---

### `kc-workspace` / `Workspace`

**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):

| Property | Attribute | Type | Description |
|---|---|---|---|
| `groups` | — | `{ id: string; userId?: undefined \| string; teamId?: undefined \| string; name: string; sortOrder: number; createdAt: string }[]` | Pre-bucketed conversation groups for the sidebar. Set as a JS property. |
| `conversations` | — | `{ id: string; title: string; groupId?: undefined \| string; scope: { type: "document" \| "collection"; documentId?: undefined \| string; filters?: undefined \| { tags?: undefined \| string[]; authors?: undefined \| string[]; contentType?: undefined \| "transcript" \| "markdown"; dateRange?: undefined \| { from: string; to: string } } }; messageCount: number; lastMessageAt: string; updatedAt: string }[]` | Flat conversation list (auto-bucketed if `groups` is empty). Set as a JS property. |
| `activeId` | `active-id` | `undefined \| string` | Id of the open conversation, highlighted in the sidebar. |
| `messages` | — | `{ id: string; role: "user" \| "assistant"; content: string; reasoning?: undefined \| { text: string; label?: undefined \| string }; tools?: undefined \| { type: string; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string }[]; attachments?: undefined \| { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]; actions?: undefined \| ("copy" \| "like" \| "dislike" \| "regenerate" \| "edit" \| { id: string; label: string; icon?: undefined \| string })[]; avatar?: undefined \| { src?: undefined \| string; fallback?: undefined \| string; alt?: undefined \| string } }[]` | The active conversation's message thread, newest last. Set as a JS property. |
| `value` | `value` | `undefined \| string` |  |
| `placeholder` | `placeholder` | `undefined \| string` |  |
| `loading` | `loading` | `undefined \| false \| true` |  |
| `suggestions` | — | `undefined \| string[]` |  |
| `suggestionMode` | `suggestion-mode` | `undefined \| "submit" \| "fill"` |  |
| `proseSize` | `prose-size` | `undefined \| "xs" \| "sm" \| "base" \| "lg"` |  |
| `codeTheme` | `code-theme` | `undefined \| string` |  |
| `codeHighlight` | `code-highlight` | `undefined \| false \| true` |  |
| `chatTitle` | `chat-title` | `undefined \| string` |  |
| `models` | — | `undefined \| { id: string; name: string; provider?: undefined \| string }[]` |  |
| `currentModel` | `current-model` | `undefined \| string` |  |
| `context` | — | `undefined \| { usedTokens: number; maxTokens: number; inputTokens?: undefined \| number; outputTokens?: undefined \| number; estimatedCost?: undefined \| number }` |  |
| `scrollButton` | `scroll-button` | `undefined \| false \| true` |  |
| `search` | `search` | `undefined \| false \| true` |  |
| `voice` | `voice` | `undefined \| false \| true` |  |
| `slashCommands` | — | `undefined \| { id: string; label: string; description?: undefined \| string; category?: undefined \| string }[]` |  |
| `slashActiveIds` | — | `undefined \| string[]` |  |
| `slashCompact` | `slash-compact` | `undefined \| false \| true` |  |
| `sidebarWidth` | `sidebar-width` | `undefined \| number` | Sidebar default width as a percent of the workspace (default 22). |
| `sidebarMinWidth` | `sidebar-min-width` | `undefined \| number` | Sidebar min width in px (default 200). |
| `sidebarMaxWidth` | `sidebar-max-width` | `undefined \| number` | Sidebar max width in px (default 420). |
| `sidebarCollapsed` | `sidebar-collapsed` | `undefined \| false \| true` | Initial collapsed state of the sidebar (default false). |

**Events** (non-bubbling `CustomEvent`s — listen directly on the element):

| Event | `detail` type | Description |
|---|---|---|
| `conversationselect` | `CustomEvent<{ id: string }>` | A conversation was selected in the sidebar. |
| `messageaction` | `CustomEvent<{ messageId: string; action: string }>` | An action button on a message was clicked. |
| `modelchange` | `CustomEvent<{ modelId: string }>` | The header model switcher changed. |
| `newchat` | `CustomEvent<Record<string, never>>` | The "New chat" button was clicked. |
| `search` | `CustomEvent<Record<string, never>>` | The Search button was clicked. |
| `sidebartoggle` | `CustomEvent<{ collapsed: false \| true }>` | The sidebar was collapsed or expanded. |
| `slashselect` | `CustomEvent<{ command: { id: string; label: string; description?: undefined \| string; category?: undefined \| string } }>` | A slash command was chosen from the palette. |
| `submit` | `CustomEvent<{ value: string; attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | User submitted a message. |
| `suggestionclick` | `CustomEvent<{ value: string }>` | A suggestion chip was clicked (only in `suggestion-mode="fill"`). |
| `valuechange` | `CustomEvent<{ value: string }>` | Fired on every input change. |
| `voice` | `CustomEvent<Record<string, never>>` | The Mic / voice button was clicked. |

---

