# @kitn.ai/ui > Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. 80 custom elements, every one prefixed `kai-` (e.g. ``, ``): streaming responses, markdown + code rendering, reasoning/tool panels, attachments, conversation sidebar, voice input. Zero framework dependency for consumers; the SolidJS runtime it is authored in is bundled in, so the host needs nothing. ## Install ```bash npm install @kitn.ai/ui # SolidJS consumers also need the peer dep: npm install solid-js ``` ## #1 rule: array/object data goes on JS PROPERTIES, not HTML attributes This is the single most common mistake. Arrays and objects (`messages`, `models`, `context`, `suggestions`, `triggers`, …) MUST be assigned as JavaScript properties on the element. They CANNOT be passed as HTML attributes — an HTML attribute is always a string and will be ignored or mis-parsed. ```js const chat = document.querySelector('kai-chat'); chat.messages = [{ id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Hi!' }] }]; // ✅ property ``` ```html ``` Only scalar values (string/number/boolean) work as attributes (e.g. `placeholder`, `loading`, `theme`). ## Two layers **Layer 1 — batteries-included web components** (`import '@kitn.ai/ui/elements'`): Drop an element into any framework (React, Vue, plain HTML). Data in via JS properties; interactions out via non-bubbling CustomEvents. - `` — full chat UI (message list + prompt input). The primary starting point. - `` — sidebar conversation browser with group support. - `` — standalone composer with send button. **Layer 2 — composable primitives** (`import { … } from '@kitn.ai/ui'`): All 80 elements are also exported individually. Use them for custom layouts or features `` does not expose (ChainOfThought, FeedbackBar, ThinkingBar, VoiceInput, …). Your bundler tree-shakes the rest. ## Key rules for the web components 1. **Array/object data = JS properties** (see above). Scalars may be attributes. 2. **Events are non-bubbling `CustomEvent`s** — listen directly on the element: `chat.addEventListener('kai-submit', (e) => console.log(e.detail.value))` 3. **`theme` attribute** (`'light' | 'dark' | 'auto'`) works on every element. Default `auto` follows `prefers-color-scheme`. 4. **Theming via CSS custom properties** — override `--kai-color-*` tokens on `:root`; they pierce Shadow DOM. ## ChatMessage schema (required for ``) A message's content is an **ordered `parts` array**. There is no `content` string: it was removed in 0.20.0. Text, reasoning, tool calls, generative-UI cards, citations and file attachments all live in `parts`, in the order the model produced them, so a post-tool answer renders below its tool panel instead of being glued onto the pre-tool text. ```ts interface ChatMessage { id: string; role: 'user' | 'assistant'; /** The ONLY content channel. Ordered. */ parts: MessagePart[]; /** Action buttons under the message. Chrome, not content. */ actions?: ('copy' | 'like' | 'dislike' | 'regenerate' | 'edit')[]; avatar?: { src?: string; fallback?: string; alt?: string }; feedback?: 'like' | 'dislike'; } /** Six variants, one per kind of content. Every variant may also carry `raw` * (`{ source, payload }`), the untranslated provider block the part was * normalized from, for echoing a turn back to the model verbatim. */ type MessagePart = | { type: 'text'; text: string; raw?: RawOrigin } | { type: 'reasoning'; text: string; label?: string; index?: number; signature?: string; raw?: RawOrigin } | { type: 'tool'; tool: ToolPart; raw?: RawOrigin } | { type: 'card'; envelope: CardEnvelope; raw?: RawOrigin } | { type: 'source'; source: MessageSource; raw?: RawOrigin } | { type: 'file'; attachment: AttachmentData; raw?: RawOrigin }; interface ToolPart { type: string; /** Rendering classification. Derived from `type` when you omit it; an explicit * value you set is preserved across later patches. */ kind?: 'command' | 'file-change' | 'search' | 'fetch' | 'mcp' | 'image' | 'generic'; state: 'input-streaming' | 'input-available' | 'output-available' | 'output-error'; input?: Record; /** Raw accumulated argument fragments, for character-level streaming. */ rawInput?: string; output?: Record; toolCallId?: string; errorText?: string; raw?: RawOrigin; } /** A citation. Exported as `MessageSource` (the bare `Source` name belongs to * the citation-chip component). */ interface MessageSource { id?: string; url?: string; title?: string; snippet?: string; index?: number } ``` The simplest possible message, and one with reasoning + a tool call before its answer: ```js { id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Hi!' }] } { id: '2', role: 'assistant', parts: [ { type: 'reasoning', text: 'I should search for current pricing.' }, { type: 'tool', tool: { type: 'search', state: 'output-available', toolCallId: 'tc_1', input: { query: 'current pricing' }, output: { results: ['A', 'B'] } } }, { type: 'text', text: 'Here is what I found.' }, ], } ``` Types are importable: `import type { ChatMessage, MessagePart, MessageSource } from '@kitn.ai/ui'` (also from `'@kitn.ai/ui/react'` and `'@kitn.ai/ui/state'`). ## Framework wiring **Plain HTML / CDN** ```html ``` **React** — typed wrappers auto-set properties and expose `on` props: ```tsx import { Chat } from '@kitn.ai/ui/react'; send(e.detail.value)} /> ``` **Vue** — use the element directly; pass arrays via `.prop`: ```vue ``` ## Theming ```css :root { --kai-color-background: #0f0f0f; --kai-color-primary: #7c3aed; --kai-color-muted: #1e1e1e; } ``` For plain HTML/CDN: ``. For Tailwind builds: `@import "@kitn.ai/ui/theme.css"` in your CSS. ## Docs - Full element reference (all 80 elements, every prop/event): ./llms-full.txt — https://kitn.dev/llms-full.txt - Machine-readable Custom Elements Manifest: https://unpkg.com/@kitn.ai/ui/dist/custom-elements.json - Working examples: https://github.com/kitn-ai/ui/tree/main/examples - Storybook: https://storybook.kitn.dev - Repository: https://github.com/kitn-ai/ui