Getting Started
Drop <kai-chat> into a page, wire two things, and you have a live chat UI. This guide goes from install to an element that handles real submissions.
Install
Section titled “Install”npm install @kitn.ai/uiThat’s it for web components — SolidJS ships inside the bundle. (You only add solid-js separately if you import the native Solid components; see the SolidJS guide.)
Register the elements
Section titled “Register the elements”One import registers every kai-* element in the browser’s custom element registry. Run it once — at the top of your entry file, or in a <script type="module">:
import '@kitn.ai/ui/elements';Your first chat
Section titled “Your first chat”You’ll lean on two patterns over and over: set rich data on a JavaScript property, and listen for the element’s CustomEvents. Here they are in plain HTML.
<kai-chat id="chat" style="display:block; height:100dvh;"></kai-chat>
<script type="module"> import '@kitn.ai/ui/elements';
const chat = document.getElementById('chat');
// Seed the thread. Arrays can't be attributes, so this goes in JavaScript. chat.messages = [ { id: '1', role: 'assistant', content: 'Hello! How can I help?' }, ];
// kai-submit fires when the user sends. chat.addEventListener('kai-submit', (e) => { const userMessage = { id: crypto.randomUUID(), role: 'user', content: e.detail.value }; chat.messages = [...chat.messages, userMessage]; // Call your model here, then append an assistant message. });</script>From that example:
messagesis a property. Assign a new array to trigger a re-render — mutating in place won’t.kai-submitcarries the input one.detail.value, and any staged files one.detail.attachments.- Give the element an explicit height. It fills its block, scrolling and all.
Stream a reply
Section titled “Stream a reply”<kai-chat> is transport-agnostic — it renders whatever you hand it. To stream, push an empty assistant message, then swap it for a fresh object on each chunk.
chat.addEventListener('kai-submit', async (e) => { const userText = e.detail.value.trim(); if (!userText) return;
// 1. Show the user's message and an empty assistant placeholder. const assistantId = crypto.randomUUID(); chat.messages = [ ...chat.messages, { id: crypto.randomUUID(), role: 'user', content: userText }, { id: assistantId, role: 'assistant', content: '' }, ]; chat.loading = true;
// 2. Stream from your endpoint (or a proxy to your model provider). const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: userText }), });
const reader = res.body.getReader(); const decoder = new TextDecoder(); let answer = '';
while (true) { const { value, done } = await reader.read(); if (done) break; answer += decoder.decode(value, { stream: true });
// Replace with a new object each chunk — mutation in place won't re-render. chat.messages = chat.messages.map((m) => m.id === assistantId ? { ...m, content: answer } : m ); }
chat.loading = false;});In your framework
Section titled “In your framework”The element behaves the same everywhere. Only the binding syntax differs.
<kai-chat id="chat" style="display:block; height:100dvh;"></kai-chat>
<script type="module"> import '@kitn.ai/ui/elements'; const chat = document.getElementById('chat'); chat.messages = [{ id: '1', role: 'assistant', content: 'Hello!' }]; chat.addEventListener('kai-submit', (e) => console.log(e.detail.value));</script>import { Chat } from '@kitn.ai/ui/react';
export function App() { const messages = [{ id: '1', role: 'assistant', content: 'Hello!' }]; return ( <Chat messages={messages} style={{ display: 'block', height: '100dvh' }} onSubmit={(e) => console.log(e.detail.value)} /> );}<template> <kai-chat :messages.prop="messages" style="display:block; height:100dvh;" @kai-submit="onSubmit" /></template>
<script setup>import '@kitn.ai/ui/elements';const messages = [{ id: '1', role: 'assistant', content: 'Hello!' }];const onSubmit = (e) => console.log(e.detail.value);</script><script> import '@kitn.ai/ui/elements'; const messages = [{ id: '1', role: 'assistant', content: 'Hello!' }];</script>
<kai-chat {messages} style="display:block; height:100dvh;" on:kai-submit={(e) => console.log(e.detail.value)}></kai-chat><kai-chat [messages]="messages" style="display:block; height:100dvh;" (kai-submit)="onSubmit($event)"></kai-chat>// Solid apps can compose the native components instead of the element.import { createSignal } from 'solid-js';import '@kitn.ai/ui/theme.css';import { ChatConfig, ChatContainer, ChatContainerContent, Message, MessageContent, PromptInput, PromptInputTextarea, PromptInputActions } from '@kitn.ai/ui';
function App() { const [input, setInput] = createSignal('');
return ( <ChatConfig> <ChatContainer style={{ height: '100dvh' }}> <ChatContainerContent> <Message> <MessageContent markdown>Hello! How can I help?</MessageContent> </Message> </ChatContainerContent> </ChatContainer> <PromptInput value={input()} onValueChange={setInput} onSubmit={() => { console.log(input()); setInput(''); }} > <PromptInputTextarea placeholder="Ask anything…" /> <PromptInputActions /> </PromptInput> </ChatConfig> );}What’s next
Section titled “What’s next”- Frameworks — idiomatic wiring for React, Vue, Angular, Svelte, and SolidJS.
- Components — full props, events, and examples for
kai-chatand every other element.