Skip to content
kitn AI/UI

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.

Terminal window
npm install @kitn.ai/ui

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.

src/main.ts
import '@kitn.ai/ui/elements'; // must come before createApp
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

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.

vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('kai-'),
},
},
}),
],
});

Add this reference once to give Volar and the template compiler full type coverage for every kai-* element’s attributes, properties, and events:

env.d.ts
/// <reference types="@kitn.ai/ui/elements" />

The rule for every element: rich data (arrays, objects) goes in as DOM properties; interactions come out as CustomEvents.

What you’re bindingVue syntaxExample
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.

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

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

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.