Vue
Drop kai-* web components directly into Vue 3 templates. Bind arrays and objects with the .prop modifier; listen with @kai-event. No adapter needed.
Install
Section titled “Install”npm install @kitn.ai/uiRegister elements
Section titled “Register elements”Import the element bundle once, before createApp().mount(). Vue stamps tags at mount time — if elements register after mount, array and object props passed by Vue get clobbered and the UI renders blank.
import '@kitn.ai/ui/elements'; // must come before createAppimport { createApp } from 'vue';import App from './App.vue';
createApp(App).mount('#app');Configure Vite
Section titled “Configure Vite”Tell Vue’s template compiler that kai-* tags are native web components, not Vue components. Without this, you’ll see “Unknown custom element” warnings and .prop bindings may misbehave.
import { defineConfig } from 'vite';import vue from '@vitejs/plugin-vue';
export default defineConfig({ plugins: [ vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('kai-'), }, }, }), ],});TypeScript support
Section titled “TypeScript support”Add this reference once to give Volar and the template compiler full type coverage for every kai-* element’s attributes, properties, and events:
/// <reference types="@kitn.ai/ui/elements" />Binding rules
Section titled “Binding rules”The rule for every element: rich data (arrays, objects) goes in as DOM properties; interactions come out as CustomEvents.
| What you’re binding | Vue syntax | Example |
|---|---|---|
| Array or object | :prop.prop="value" | :messages.prop="messages" |
| String / boolean / number | :attr="value" or attr="literal" | :active-id="activeId" |
| Event | @kai-event-name="handler" | @kai-submit="onSubmit" |
The .prop modifier forces Vue to set the value as a live DOM property rather than serialising it to an HTML attribute string. Always use it for arrays and objects.
A working chat
Section titled “A working chat”<kai-chat> is transport-agnostic: give it a messages array, handle @kai-submit, and stream your model’s reply back into state. The element owns the UI; you own the request.
<script setup lang="ts">import '@kitn.ai/ui/elements';import { ref } from 'vue';
type Message = { id: string; role: 'user' | 'assistant'; content: string };
const messages = ref<Message[]>([ { id: '1', role: 'assistant', content: 'Hello! How can I help?' },]);
const onSubmit = async (e: CustomEvent<{ value: string }>) => { const userMsg = { id: crypto.randomUUID(), role: 'user' as const, content: e.detail.value }; const history = [...messages.value, userMsg]; messages.value = history;
const aid = crypto.randomUUID(); messages.value = [...history, { id: aid, role: 'assistant', content: '' }];
let answer = ''; for await (const token of streamFromYourAPI(history)) { answer += token; messages.value = messages.value.map((m) => m.id === aid ? { ...m, content: answer } : m, ); }};</script>
<template> <div style="display: flex; flex-direction: column; height: 100dvh;"> <kai-chat :messages.prop="messages" :suggestions.prop="['Summarize this chat', 'Start fresh']" style="flex: 1; min-height: 0;" @kai-submit="onSubmit" /> </div></template>Compose your own layout
Section titled “Compose your own layout”<kai-chat> is one option, not the only one. Every element composes independently. Here’s a sidebar + thread layout:
<script setup lang="ts">import '@kitn.ai/ui/elements';import { ref } from 'vue';
const conversations = ref([ { id: 'c1', title: 'First chat', scope: { type: 'document' }, messageCount: 3, lastMessageAt: '2026-06-01T12:00:00Z', updatedAt: '2026-06-01T12:00:00Z', },]);const activeId = ref('c1');const messages = ref([{ id: '1', role: 'assistant', content: 'Hi!' }]);
const onSelect = (e: CustomEvent<{ id: string }>) => { activeId.value = e.detail.id; // load messages for the selected conversation};
const onSubmit = (e: CustomEvent<{ value: string }>) => { // send message and stream reply};</script>
<template> <div style="display: flex; height: 100dvh;"> <kai-conversations :conversations.prop="conversations" :active-id="activeId" style="width: 280px; flex-shrink: 0;" @kai-conversation-select="onSelect" @kai-new-chat="startNewConversation" /> <kai-chat :messages.prop="messages" style="flex: 1; min-width: 0;" @kai-submit="onSubmit" /> </div></template>You can also drop standalone display elements — <kai-markdown>, <kai-code-block>, <kai-artifact>, <kai-reasoning> — anywhere in your own UI to render rich AI content without adopting the full chat shell.
Theming
Section titled “Theming”Each element is styled inside its own Shadow DOM. No CSS import is required for the elements themselves. Pull in @kitn.ai/ui/theme.css only when you want to override design tokens:
import '@kitn.ai/ui/theme.css';See the Theming guide for available tokens.