HTML
Drop <kai-*> web components into any HTML page with a single import. No framework, no build configuration — the elements register themselves globally and work in every modern browser.
Install
Section titled “Install”npm install @kitn.ai/uiRegister all web components once with a side-effect import near your app entry:
import '@kitn.ai/ui/elements';Every <kai-*> tag is then available globally — drop them anywhere in your HTML.
The property / event contract
Section titled “The property / event contract”Every kai-* web component follows the same two rules:
- Rich data (arrays, objects) → JS properties. Set them in a
<script>block, not as HTML attributes. Attributes only work for scalars (strings, booleans, numbers). - Interactions →
addEventListener. All events areCustomEvents dispatched on the element itself. They do not bubble.
// Arrays and objects — set the property in JavaScript, not as an attributeel.messages = [{ id: '1', role: 'assistant', content: 'Hi' }];
// Scalars — fine as attributes or propertiesel.setAttribute('theme', 'dark');
// Events — always addEventListener; they do not bubbleel.addEventListener('kai-submit', (e) => console.log(e.detail.value));Reactivity: always assign a new array (and a new object for any message you change). Mutating an existing array or object in place will not trigger a re-render.
Complete example — <kai-chat>
Section titled “Complete example — <kai-chat>”<kai-chat> is the batteries-included shell: give it a messages array, handle the kai-submit event, and stream your model’s reply back. You own the request; the element owns the UI.
<!DOCTYPE html><html><head> <style> html, body { margin: 0; height: 100%; } .app { display: flex; flex-direction: column; height: 100dvh; } </style></head><body> <div class="app"> <kai-chat id="chat" style="flex: 1; min-height: 0;"></kai-chat> </div>
<script type="module"> import '@kitn.ai/ui/elements';
const chat = document.getElementById('chat');
// Seed an initial assistant message chat.messages = [ { id: crypto.randomUUID(), role: 'assistant', content: 'Hello! How can I help?', actions: ['copy', 'like', 'dislike'], }, ]; chat.suggestions = ['Summarize the chat', 'Start fresh'];
chat.addEventListener('kai-submit', async (e) => { const text = e.detail.value;
// Append the user message — always assign a new array const history = [ ...chat.messages, { id: crypto.randomUUID(), role: 'user', content: text }, ]; chat.messages = history; chat.loading = true;
// Create 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; // Replace only the placeholder — every other message stays the same chat.messages = chat.messages.map((m) => m.id === aid ? { ...m, content: answer } : m, ); }
chat.loading = false; }); </script></body></html>Compose individual elements
Section titled “Compose individual elements”<kai-chat> is one option, not the only one. Every element can be placed independently. Here’s a sidebar + thread layout using <kai-conversations> and <kai-chat>:
<style> html, body { margin: 0; height: 100%; } .workspace { display: flex; height: 100dvh; }</style>
<div class="workspace"> <kai-conversations id="sidebar" style="width: 280px; flex-shrink: 0;"></kai-conversations> <kai-chat id="chat" style="flex: 1; min-width: 0;"></kai-chat></div>
<script type="module"> import '@kitn.ai/ui/elements';
const sidebar = document.getElementById('sidebar'); const chat = document.getElementById('chat');
sidebar.conversations = myConversations; chat.messages = loadMessages(myConversations[0]?.id);
sidebar.addEventListener('kai-conversation-select', (e) => { chat.messages = loadMessages(e.detail.id); });
sidebar.addEventListener('kai-new-chat', () => startNewConversation());
chat.addEventListener('kai-submit', (e) => sendMessage(e.detail.value));</script>Add a resizable divider
Section titled “Add a resizable divider”Wrap panels in <kai-resizable> with one <kai-resizable-item> each — drag handles are inserted automatically. Each item accepts a size (px or %) and optional min/max. Listen for kai-change to persist the layout.
<style> html, body { margin: 0; height: 100%; } .app { display: flex; flex-direction: column; height: 100dvh; }</style>
<div class="app"> <kai-resizable orientation="horizontal" style="flex: 1; min-height: 0;"> <kai-resizable-item size="25%" min="200px"> <kai-conversations id="sidebar"></kai-conversations> </kai-resizable-item> <kai-resizable-item> <kai-chat id="chat"></kai-chat> </kai-resizable-item> </kai-resizable></div>
<script type="module"> import '@kitn.ai/ui/elements';
const resizable = document.querySelector('kai-resizable'); resizable.addEventListener('kai-change', (e) => { localStorage.setItem('panel-sizes', JSON.stringify(e.detail.sizes)); });</script>Standalone display elements
Section titled “Standalone display elements”You can drop any single element into an existing page without adopting the full chat shell. <kai-markdown>, <kai-code-block>, and <kai-artifact> are common standalone picks for rendering AI-generated content:
<script type="module"> import '@kitn.ai/ui/elements';</script>
<kai-markdown content="## Result\n\nHere is your **summary**."></kai-markdown><kai-code-block code="const greet = (name) => `Hello, ${name}!`;" language="js"></kai-code-block>Each element fills its container and is controlled entirely through properties and events.
Key events reference
Section titled “Key events reference”| Element | Event | detail |
|---|---|---|
kai-chat | kai-submit | { value: string } |
kai-chat | kai-model-change | { model: string } |
kai-conversations | kai-conversation-select | { id: string } |
kai-conversations | kai-new-chat | — |
kai-conversations | kai-toggle-sidebar | — |
kai-resizable | kai-change | { sizes: string[] } |
For the full API of any element — all properties, events, and their types — see the individual component page in the Components reference.
Theming
Section titled “Theming”By default each element is styled inside its own Shadow DOM and needs no external CSS. To override design tokens (colors, radii, spacing), load theme.css:
<!-- bundler --><link rel="stylesheet" href="./node_modules/@kitn.ai/ui/theme.css" />
<!-- CDN --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@kitn.ai/ui/theme.css" />Then override any --color-* or --radius-* custom properties on :root or a scoping selector.