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';
await customElements.whenDefined('kai-chat');
const chat = document.getElementById('chat');
// Seed the thread. Arrays can't be attributes, so this goes in JavaScript. chat.messages = [ { id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Hello! How can I help?' }] }, ];
// kai-submit fires when the user sends. chat.addEventListener('kai-submit', (e) => { const userMessage = { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 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. Two imports do the streaming: createAssistantStream owns the in-flight assistant message, readOpenAIStream parses the SSE onto it.
import { createAssistantStream } from '@kitn.ai/ui/state';import { readOpenAIStream, toOpenAIMessages } from '@kitn.ai/ui/wire';
chat.addEventListener('kai-submit', async (e) => { const userText = e.detail.value.trim(); if (!userText) return;
// 1. Show the user's message. const history = [ ...chat.messages, { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: userText }] }, ]; chat.messages = history; chat.loading = true;
// 2. Stream from your endpoint (or a proxy to your model provider). const stream = createAssistantStream((update) => { chat.messages = update(chat.messages); });
try { const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: toOpenAIMessages(history) }), }); await readOpenAIStream(res, stream); } finally { stream.done(); chat.loading = false; }});That covers text, reasoning and tool calls. For the tool loop, Anthropic streams and the error model, see the wire adapter recipe.
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';
await customElements.whenDefined('kai-chat');
const chat = document.getElementById('chat'); chat.messages = [{ id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Hello!' }] }]; chat.addEventListener('kai-submit', (e) => console.log(e.detail.value));</script>import { Chat } from '@kitn.ai/ui/react';import type { ChatMessage } from '@kitn.ai/ui/react';
export function App() { const messages: ChatMessage[] = [ { id: '1', role: 'assistant', parts: [{ type: 'text', text: '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', parts: [{ type: 'text', text: 'Hello!' }] }];const onSubmit = (e) => console.log(e.detail.value);</script><script> import '@kitn.ai/ui/elements'; const messages = [{ id: '1', role: 'assistant', parts: [{ type: 'text', text: '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 { Button, ChatConfig, ChatContainer, ChatContainerContent, Message, MessageContent, PromptInput, PromptInputTextarea, PromptInputActions } from '@kitn.ai/ui/solid';
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> <Button size="sm" disabled={!input()}>Send</Button> </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.