The kai-* web components drop into React, Vue, Angular, Svelte, or plain HTML with a single import — no per-framework package, no wrappers. React adds a typed adapter; in a Solid app you can import the native components directly.
Pick an entry point
Section titled “Pick an entry point”| Entry | Use it for |
|---|---|
@kitn.ai/ui/elements | Any framework — registers every kai-* element |
@kitn.ai/ui/react | React — typed wrappers with camelCase props and event handlers |
@kitn.ai/ui/solid | SolidJS — native Solid components for full compositional control |
@kitn.ai/ui | The shared layer every framework uses — types, state and card helpers |
Any framework: the web components
Section titled “Any framework: the web components”Import @kitn.ai/ui/elements once for its side effect. It registers every kai-* element; after that they work like built-in HTML.
import '@kitn.ai/ui/elements';Registration is async. If your framework sets array or object properties as it mounts (Vue, Angular, Svelte all do), mount behind the elementsReady promise the same entry exports. A property written before the element upgrades is lost.
import { elementsReady } from '@kitn.ai/ui/elements';
elementsReady.then(() => mountYourApp());Then use them in your templates:
<kai-chat style="display:block; height:100%;"></kai-chat>
<script type="module"> import '@kitn.ai/ui/elements';
await customElements.whenDefined('kai-chat');
const chat = document.querySelector('kai-chat'); chat.messages = [{ id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Hello!' }] }]; chat.addEventListener('kai-submit', (e) => console.log(e.detail));</script><script setup>import '@kitn.ai/ui/elements';const messages = ref([{ id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Hello!' }] }]);</script>
<template> <kai-chat :messages.prop="messages" @kai-submit="onSubmit" /></template><script>import '@kitn.ai/ui/elements';let messages = [{ id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Hello!' }] }];</script>
<kai-chat {messages} on:kai-submit={onSubmit}></kai-chat>import { elementsReady } from '@kitn.ai/ui/elements';elementsReady.then(() => bootstrapApplication(AppComponent, appConfig));// the component using kai-* tags@Component({ /* … */ schemas: [CUSTOM_ELEMENTS_SCHEMA] })export class AppComponent {}<!-- template --><kai-chat [messages]="messages" (kai-submit)="onSubmit($event)"></kai-chat>Properties vs. attributes
Section titled “Properties vs. attributes”The one thing to get right: Arrays and objects must be set in JavaScript, not as HTML attributes — an attribute is always a string, so writing messages or models as an attribute silently does nothing.
const chat = document.querySelector('kai-chat');
// Right: set the array in JavaScript.chat.messages = [{ id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Hello!' }] }];
// Wrong: an attribute is a string, so the element never gets the array.// <kai-chat messages="[...]"></kai-chat>Scalars — placeholder, loading, theme, prose-size — work fine as attributes. Each framework’s binding picks the right path for you: Vue uses :prop.prop, Angular uses [prop], Svelte binds objects directly.
Events
Section titled “Events”Every kai-* element dispatches CustomEvents with a detail payload, named with a kai- prefix (kai-submit, kai-model-change, kai-remove, …).
chat.addEventListener('kai-submit', (e) => { const { value } = e.detail; // send value to your model API});The events don’t bubble — listen on the element itself.
React adapter
Section titled “React adapter”@kitn.ai/ui/react exports a typed component for every element. Props take arrays and objects directly (no .prop modifier), and events arrive as camelCase handler props that receive the CustomEvent.
import { Chat } from '@kitn.ai/ui/react';
export function MyChatApp() { return ( <Chat messages={messages} onSubmit={(e) => console.log(e.detail.value)} onModelChange={(e) => console.log(e.detail)} /> );}SolidJS native components
Section titled “SolidJS native components”Writing Solid? Skip the element wrapper and compose the kit’s native components from @kitn.ai/ui/solid. That entry is the complete Solid catalog, compiled as its own bundle so the other frameworks never pay for it. It tree-shakes to exactly what you import and mixes freely with your own primitives.
import { createSignal } from 'solid-js';import { Button, ChatConfig, ChatContainer, ChatContainerContent, Message, MessageContent, PromptInput, PromptInputTextarea, PromptInputActions,} from '@kitn.ai/ui/solid';
export 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={() => { handleSubmit(input()); setInput(''); }} > <PromptInputTextarea placeholder="Ask anything…" /> <PromptInputActions> <Button size="sm" disabled={!input()}>Send</Button> </PromptInputActions> </PromptInput> </ChatConfig> );}Solid usage also needs solid-js as a peer dependency:
npm install solid-jsUnlike the elements, the native components are Tailwind v4 classes, so this path also needs Tailwind in the app plus an @source line pointing at the package. Rendering a real thread means MessageBody over each message’s ordered parts, or Thread for the whole list at once. Both are covered in the SolidJS guide.
Per-framework guides
Section titled “Per-framework guides”Full setup, binding patterns, and examples for each environment: