kai-workspace
The entire chat application shell as one tag — collapsible drag-resizable sidebar, conversation list, and full message thread, isolated in Shadow DOM and droppable into any framework.
- Shadow DOM
- 11 events
- Drag-resizable sidebar
- Collapsible sidebar
- 26 props
Preview
Section titled “Preview”Set data in JavaScript and listen for CustomEvents on the element:
<kai-workspace id="workspace" style="display:block; height:100vh;"></kai-workspace>
<script type="module"> import '@kitn.ai/ui/elements';
await customElements.whenDefined('kai-workspace');
const ws = document.getElementById('workspace');
ws.conversations = [ { id: '1', title: 'Web component architecture', scope: { type: 'document' }, messageCount: 12, lastMessageAt: '2026-06-16T10:00:00Z', updatedAt: '2026-06-16T10:00:00Z', }, ]; ws.messages = [ { id: 'm1', role: 'user', parts: [{ type: 'text', text: 'How do I drop the whole chat app in with one tag?' }] }, { id: 'm2', role: 'assistant', parts: [{ type: 'text', text: 'Use <kai-workspace> and set the three core properties.' }] }, ]; ws.models = [{ id: 'claude-4-opus', name: 'Claude 4 Opus', provider: 'Anthropic' }]; ws.activeId = '1';
ws.addEventListener('kai-conversation-select', (e) => console.log('selected:', e.detail.id)); ws.addEventListener('kai-submit', (e) => console.log('user sent:', e.detail.value)); ws.addEventListener('kai-sidebar-toggle', (e) => console.log('collapsed:', e.detail.collapsed));</script>- Properties vs attributes —
groups,conversations,messages,models,suggestions,triggers,kindIcons, andcontextare arrays and objects, so set them in JavaScript. Scalar props (placeholder,loading, sidebar dimensions, etc.) work as attributes or properties. - Sidebar — drag-resizable and collapsible. Constrain with
sidebarWidth(a percent of the workspace, default26),sidebarMinWidthandsidebarMaxWidth(both px, default240/420). Collapse is uncontrolled by default — the element manages it and fireskai-sidebar-toggle. Start collapsed with thedefault-sidebar-collapsedattribute, or control it by setting thesidebarCollapsedproperty and updating it fromkai-sidebar-toggle(lets you persist the choice or collapse from outside the header). - Conversation grouping —
groupssupplies the section headers,conversationssupplies the rows, and each row lands in the group whoseidmatches itsgroupId. Leavegroupsoff and the whole list renders in one Ungrouped section.
Examples
Section titled “Examples”Default
Section titled “Default”Loading State
Section titled “Loading State”Set loading while an assistant reply is streaming.
Model Switcher
Section titled “Model Switcher”Set the models property to surface a model picker; listen for kai-model-change to update your store.
Starter Suggestions
Section titled “Starter Suggestions”Set suggestions to show prompt chips on an empty thread. suggestion-mode="submit" fires kai-submit; "fill" populates the input and fires kai-suggestion-click.
Entity Pills
Section titled “Entity Pills”Set triggers so / inserts a skill pill and @ opens a sectioned menu of agents and plugins. kai-submit carries the structured doc + entities.
Sidebar Starts Collapsed
Section titled “Sidebar Starts Collapsed”A floating reveal button appears at the top-left; kai-sidebar-toggle fires on every change.
| Property | Type | Default | Notes |
|---|---|---|---|
| theme | "light" | "dark" | "auto" | 'auto' | Color mode (`auto` follows prefers-color-scheme). |
| groups | [] | The sidebar's section headers, 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 sidebar. Set as a JS property. | |
| conversations | [] | Every conversation in the sidebar, 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 (headerless when `compact`), so nothing you pass in is ever dropped. There is no recency bucketing. Set as a JS property. Omit for an empty sidebar, or when `no-conversations` replaces the built-in list with your own `sidebar-header` content. | |
| activeId | string | — | Id of the open conversation, highlighted in the sidebar. |
| messages | [] | The active conversation's message thread, newest last. Set as a JS property (`el.messages = [...]`); a NEW array reference per streaming chunk re-renders (mutating in place does not). Omit for an empty thread. | |
| value | string | — | |
| placeholder | string | 'Send a message...' | |
| loading | boolean | false | |
| suggestions | string[] | — | |
| suggestionMode | "submit" | "fill" | 'submit' | |
| proseSize | "xs" | "sm" | "base" | "lg" | 'sm' | |
| codeTheme | string | 'github-dark-dimmed' | |
| codeHighlight | boolean | true | |
| chatTitle | string | — | |
| models | — | ||
| currentModel | string | — | |
| context | — | ||
| scrollButton | boolean | true | |
| search | boolean | false | |
| voice | boolean | false | |
| triggers | — | Rich entity triggers (`/` skills, `@` agents/plugins) forwarded to the input. | |
| kindIcons | Record<string, string> | — | Default icon per entity kind (kind → image src) forwarded to the input. |
| sidebarWidth | number | 26 | Sidebar default width as a percent of the workspace (default 26). |
| sidebarMinWidth | number | 240 | Sidebar min width in px (default 240). |
| sidebarMaxWidth | number | 420 | Sidebar max width in px (default 420). |
| sidebarCollapsed | boolean | — | Controlled collapsed state. Set this as a JS property (`el.sidebarCollapsed = true`) to drive the sidebar from your app, updating it in response to the `kai-sidebar-toggle` event. Omit for uncontrolled (the element manages it). |
| defaultSidebarCollapsed | boolean | — | Initial collapsed state when uncontrolled (default false). Use the `default-sidebar-collapsed` attribute to start collapsed in plain HTML. |
| collapseBelow | number | — | Auto-collapse the rail when the workspace's own width drops below this many px, and re-expand when it grows back above. Uncontrolled only (it never fights an app-driven `sidebarCollapsed`); omit to disable. Fires `kai-sidebar-toggle`. Attribute: `collapse-below`. |
| compact | boolean | — | Render Recents as dense single-line rows (a leading dot + title, no count). |
| noConversations | boolean | — | Suppress the built-in ConversationList so the `sidebar-header` slot owns the whole rail flex region (for apps that supply their own rail nav). Default false. Attribute: `no-conversations`. |
| cardTypes | Record<string, string> | — | Optional card type -> custom-element tag overrides/additions for `card` parts (merged over the built-ins). Property: `el.cardTypes`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type. |
| cardSchemas | Record<string, object> | — | JSON Schemas for the card types this app renders, keyed by envelope type. The companion of `cardTypes`, which says what DRAWS a card while this says what a VALID one looks like. An OBJECT, so it is a JS property only: `el.cardSchemas = { 'pricing-table': pricingSchema }`, never an attribute. `createCardRegistry(...).validationSchemas` is exactly this shape. Without it the kit validates its own seven built-ins and leaves your own card type, the one your app actually cares about, as the only unchecked thing on screen. A schema here WINS over a built-in of the same name. Typed `Record<string, object>` rather than `Record<string, JsonSchema>` deliberately: an imported `.json` schema widens `"type"` to `string`, and an authored one carries `$schema`/`title`/`description`/`additionalProperties`, so the tighter type would reject both of the normal ways to supply one. |
Events
Section titled “Events”| Event | Detail | Notes |
|---|---|---|
| kai-conversation-select | | A conversation was selected in the sidebar. |
| kai-message-action | | An action button on a message was clicked. `state` is present only for the toggleable feedback votes: `'on'` when a like/dislike is set, `'off'` when re-tapped to clear. |
| kai-model-change | | The header model switcher changed. |
| kai-new-chat | Record<string, never> | The "New chat" button was clicked. |
| kai-search | Record<string, never> | The Search button was clicked. |
| kai-sidebar-toggle | | The sidebar was collapsed or expanded. |
| kai-submit | | User submitted a message. |
| kai-suggestion-click | | A suggestion chip was clicked (only in `suggestion-mode="fill"`). |
| kai-value-change | | Fired on every input change. |
| kai-voice | Record<string, never> | The Mic / voice button was clicked. |
Methods
Section titled “Methods”| Method | Signature | Notes |
|---|---|---|
| toggleSidebar | (): void | Collapse/expand the conversation sidebar and fire `kai-sidebar-toggle`. |
| collapseSidebar | (): void | Force the conversation sidebar collapsed (fires `kai-sidebar-toggle`). |
| expandSidebar | (): void | Force the conversation sidebar expanded (fires `kai-sidebar-toggle`). |
| focus | (options?: FocusOptions): void | Focus the thread's composer. |
| clear | (): void | Clear the thread draft + staged attachments. |
| send | (): void | Submit the current thread draft programmatically (fires `kai-submit`). |
| scrollToBottom | (behavior?: ScrollBehavior): void | Scroll the thread to the newest message. |
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.