Skip to content
kitn AI/UI

Workspace

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

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 attributesgroups, conversations, messages, models, suggestions, triggers, kindIcons, and context are 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, default 26), sidebarMinWidth and sidebarMaxWidth (both px, default 240 / 420). Collapse is uncontrolled by default — the element manages it and fires kai-sidebar-toggle. Start collapsed with the default-sidebar-collapsed attribute, or control it by setting the sidebarCollapsed property and updating it from kai-sidebar-toggle (lets you persist the choice or collapse from outside the header).
  • Conversation groupinggroups supplies the section headers, conversations supplies the rows, and each row lands in the group whose id matches its groupId. Leave groups off and the whole list renders in one Ungrouped section.

Set loading while an assistant reply is streaming.

Set the models property to surface a model picker; listen for kai-model-change to update your store.

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.

Set triggers so / inserts a skill pill and @ opens a sectioned menu of agents and plugins. kai-submit carries the structured doc + entities.

A floating reveal button appears at the top-left; kai-sidebar-toggle fires on every change.

PropertyTypeDefaultNotes
theme'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.
activeIdId 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
placeholder'Send a message...'
loadingfalse
suggestions
suggestionMode'submit'
proseSize'sm'
codeTheme'github-dark-dimmed'
codeHighlighttrue
chatTitle
models
currentModel
context
scrollButtontrue
searchfalse
voicefalse
triggersRich entity triggers (`/` skills, `@` agents/plugins) forwarded to the input.
kindIconsDefault icon per entity kind (kind → image src) forwarded to the input.
sidebarWidth26Sidebar default width as a percent of the workspace (default 26).
sidebarMinWidth240Sidebar min width in px (default 240).
sidebarMaxWidth420Sidebar max width in px (default 420).
sidebarCollapsedControlled 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).
defaultSidebarCollapsedInitial collapsed state when uncontrolled (default false). Use the `default-sidebar-collapsed` attribute to start collapsed in plain HTML.
collapseBelowAuto-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`.
compactRender Recents as dense single-line rows (a leading dot + title, no count).
noConversationsSuppress 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`.
cardTypesOptional 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.
cardSchemasJSON 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.
EventDetailNotes
A conversation was selected in the sidebar.
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.
The header model switcher changed.
Record<string, never> The "New chat" button was clicked.
Record<string, never> The Search button was clicked.
The sidebar was collapsed or expanded.
User submitted a message.
A suggestion chip was clicked (only in `suggestion-mode="fill"`).
Fired on every input change.
Record<string, never> The Mic / voice button was clicked.
MethodSignatureNotes
(): voidCollapse/expand the conversation sidebar and fire `kai-sidebar-toggle`.
(): voidForce the conversation sidebar collapsed (fires `kai-sidebar-toggle`).
(): voidForce the conversation sidebar expanded (fires `kai-sidebar-toggle`).
(options?: FocusOptions): voidFocus the thread's composer.
(): voidClear the thread draft + staged attachments.
(): voidSubmit the current thread draft programmatically (fires `kai-submit`).
(behavior?: ScrollBehavior): voidScroll the thread to the newest message.

This element wraps these SolidJS components — reach for them directly when you need finer control than the props expose.

ChatThreadConversationListCollapsedRailResizablePanelGroupResizablePanelResizableHandle