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

  • 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. 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;
});

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