Skip to content
kitn AI/UI

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.

Terminal window
npm install @kitn.ai/ui

That’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.)

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';

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:

  • messages is a property. Assign a new array to trigger a re-render — mutating in place won’t.
  • kai-submit carries the input on e.detail.value, and any staged files on e.detail.attachments.
  • Give the element an explicit height. It fills its block, scrolling and all.

<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.

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>
  • Frameworks — idiomatic wiring for React, Vue, Angular, Svelte, and SolidJS.
  • Components — full props, events, and examples for kai-chat and every other element.