kai-conversations
A self-contained sidebar that renders a grouped, scrollable conversation list with a “New chat” button and collapse toggle — drop it into any framework and wire up three events.
- Shadow DOM
- 3 events
- Flat or grouped lists
- Built-in search
- Declarative child elements
Preview
Section titled “Preview”Set the data in JavaScript (it’s arrays and objects) and listen for CustomEvents:
<kai-conversations id="sidebar" style="display:block; width:300px; height:100vh;"></kai-conversations>
<script type="module"> import '@kitn.ai/ui/elements';
await customElements.whenDefined('kai-conversations');
const el = document.getElementById('sidebar');
el.conversations = [ { id: 'c-1', title: 'Web component architecture', scope: { type: 'collection' }, messageCount: 12, lastMessageAt: '2026-06-16T10:00:00.000Z', updatedAt: '2026-06-16T10:00:00.000Z', }, ]; el.activeId = 'c-1';
el.addEventListener('kai-conversation-select', (e) => { el.activeId = e.detail.id; // keep the active highlight in sync }); el.addEventListener('kai-new-chat', () => console.log('new chat')); el.addEventListener('kai-toggle-sidebar', () => console.log('toggled'));</script>Two data approaches:
- In JavaScript —
el.conversationsis the rows;el.groupsis the section headers. They work together, not as alternatives: each row lands in the group whoseidmatches itsgroupId, and anything with no match falls into a single Ungrouped section. Leavegroupsempty and the whole list renders as one Ungrouped section. - Declarative children — nest
<kai-conversation>tags directly; each carries itsidas an attribute and title as text content. The element reads them on mount and watches via MutationObserver — works in plain HTML, SSR, or any framework that sets innerHTML.
| Child element | Attributes | Text content | Notes |
|---|---|---|---|
| <kai-conversation> | group-idid | Yes | Parse a single light-DOM `<kai-conversation>` element into a `ConversationSummary`. Attribute mapping: - `id` → ConversationSummary.id - `group-id` → ConversationSummary.groupId (optional) - textContent → ConversationSummary.title Required fields not expressible as HTML attributes (`scope`, `messageCount`, `lastMessageAt`, `updatedAt`) receive safe defaults so the rendered list item is fully functional with just `id` + title text. |
Examples
Section titled “Examples”Default List
Section titled “Default List”A flat conversations array with no groups — every row renders in one Ungrouped section.
Active Conversation Highlighted
Section titled “Active Conversation Highlighted”activeId highlights the matching row and scrolls it into view.
Pre-bucketed Groups
Section titled “Pre-bucketed Groups”Supply groups for the section headers and give each conversation a matching groupId; anything unmatched falls to the “Ungrouped” section.
Empty State
Section titled “Empty State”An empty conversations array renders the built-in empty state.
Declarative Children
Section titled “Declarative Children”Nested <kai-conversation> elements — no JavaScript wiring needed.
Replace the title bar or the empty state, or add a footer row (account, settings, usage). The slot names mirror <kai-chat>, so the two compose with one vocabulary.
| Slot | Mode | Purpose |
|---|---|---|
| header | replace | Full custom title bar; replaces the built-in toggle / "Chats" / New-chat row. |
| empty | replace | Custom zero-state shown when there are no conversations; replaces the built-in "No conversations yet". |
| footer | inject | A row below the list: account, settings, or usage. |
| Property | Type | Default | Notes |
|---|---|---|---|
| theme | "light" | "dark" | "auto" | 'auto' | Color mode (`auto` follows prefers-color-scheme). |
| groups | [] | The list's section headers (`{ id, name, sortOrder, createdAt }`), rendered in array order. A group carries no conversations of its own; it is matched against `conversations` by id, so the two props are complementary rather than alternatives. Omit for an ungrouped list. Set as a JS property. | |
| conversations | [] | Every conversation the list renders, flat. Each one is filed under the group whose `id` equals its `groupId`; one with no `groupId`, or with a `groupId` matching no entry in `groups`, falls into a trailing "Ungrouped" section, so nothing you pass in is ever dropped. There is no recency bucketing. Set as a JS property. Omit to supply them as `<kai-conversation>` light-DOM children instead, or for the empty state. | |
| activeId | string | — | The id of the currently-open conversation, highlighted in the list. |
| collapsed | boolean | — | Controlled collapsed state. Set as a JS property (`el.collapsed = true`) to drive the rail from your app, updating it in response to `kai-collapse-toggle`. Omit for uncontrolled (the element manages it). Collapsed shrinks the rail to a floating reopen button. |
| defaultCollapsed | boolean | — | Initial collapsed state when uncontrolled (default false). Use the `default-collapsed` attribute to start collapsed in plain HTML. |
Events
Section titled “Events”| Event | Detail | Notes |
|---|---|---|
| kai-collapse-toggle | | The rail was collapsed or expanded (via the toggle, the reopen button, or a `collapse()`/`expand()`/`toggle()` call). |
| kai-conversation-select | | A conversation was selected. |
| kai-new-chat | Record<string, never> | The "New chat" button was clicked. |
| kai-search | | The built-in search box query changed (typing, or a programmatic `clear()` which fires it with `''`). Lets a consumer mirror or server-side the filter. |
| kai-toggle-sidebar | Record<string, never> | The sidebar toggle was clicked. |
Methods
Section titled “Methods”| Method | Signature | Notes |
|---|---|---|
| focus | (options?: FocusOptions): void | Focus the built-in search input inside the shadow root. |
| clear | (): void | Clear the internal search query (resets the list filter) and fire kai-search with an empty string. |
| select | (id: string): void | Programmatically select a conversation by id. The mirror of the kai-conversation-select event (a convenience over driving `activeId`). |
| collapse | (): void | Collapse the rail to its floating reopen button (fires `kai-collapse-toggle`). |
| expand | (): void | Expand the rail back to the full list (fires `kai-collapse-toggle`). |
| toggle | (): void | Toggle the rail collapsed/expanded (fires `kai-collapse-toggle`). |
Composed from
Section titled “Composed from”This element wraps these SolidJS components — reach for them directly when you need finer control than the props expose.