The web UI layer for AI apps. Works anywhere you can put HTML.
Threads, workspaces, artifacts, tool and reasoning panels, generative UI, voice.
In your framework: React, Next.js, Vue, Svelte, Angular, SolidJS, TanStack Start, or plain HTML.
Or on a platform you don't control: Shopify, WordPress, Webflow, Salesforce, HubSpot, or anywhere a <script> tag is the only way in.
One implementation, not one port per framework, because they're web components. The usual objections to that are answered below.
Docs · Storybook · npm · Security policy
npm create kai@latestIt asks for a framework, then for a backend where there is more than one to choose from, and writes a project that runs. Every axis with a single possible answer today — the layout, the feature set, and the backend on the frameworks that can host nothing but the local mock — it states rather than asking, so nothing gets decided for you in silence. Press Enter through every prompt and you get React, full screen, and the kit's own mock: a real streaming turn on the first npm run dev, with no API key and nothing to host.
npx create-kai@latest --list prints the frameworks, layouts, features and gateways it can scaffold today, and marks the ones it cannot. Read that rather than a list written here; the roster moves.
npm install @kitn.ai/uiOne tag gets you a working chat:
<kai-chat></kai-chat>You are not stuck with its layout. <kai-chat> is one preset over pieces you can use directly, <kai-workspace> is another, and the pieces go well past chat. Arrange your own:
<kai-resizable>
<kai-resizable-item size="280px">
<kai-conversations></kai-conversations>
</kai-resizable-item>
<kai-resizable-item>
<kai-thread></kai-thread>
<kai-prompt-input></kai-prompt-input>
</kai-resizable-item>
</kai-resizable>That is what every starter in examples/starters is, once per framework. Feed messages in, listen for kai-submit, and it's a chat app. The wiring: Compose a message thread.
Rendering goes three ways, and they are not symmetric.
| Framework | How you render |
|---|---|
| React, Next.js, TanStack Start | Typed wrappers from @kitn.ai/ui/react (<Thread>, <PromptInput>, <Conversations>), or the raw tags. The wrappers are the default and are generated from the same build as the elements, so events arrive as React props (onSubmit for kai-submit) and each one registers its own element on mount. |
| Vue, Svelte, Angular, plain HTML | The tags, after import '@kitn.ai/ui/elements'. These set DOM properties and listen for DOM events natively, so there's no wrapper layer to add. Vue needs isCustomElement in its vite config and Angular needs CUSTOM_ELEMENTS_SCHEMA; Svelte and HTML need nothing. |
| SolidJS | Genuinely different. @kitn.ai/ui/solid hands you the SolidJS components themselves, with no custom elements in the picture. Solid is the layer everything else is built from. |
Wiring an element by hand, in any framework:
<script type="module">
import '@kitn.ai/ui/elements';
// Registration is async (that's what keeps the import SSR-safe), so wait for
// the element before setting properties or the upgrade clobbers them.
await customElements.whenDefined('kai-thread');
// A message's content is an ordered `parts` array.
document.querySelector('kai-thread').messages = [
{ id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Ask me anything.' }] },
];
</script>The same thing per framework: Getting started.
| Import | What it gives you |
|---|---|
@kitn.ai/ui/state |
Pure folds over ChatMessage[]: createAssistantStream, appendTextPart, upsertToolPart. No I/O. |
@kitn.ai/ui/wire |
Provider SSE in, message parts out: readOpenAIStream, readAnthropicStream, toOpenAIMessages. |
@kitn.ai/ui/theme.css |
Tailwind v4 token source for your own markup (theme.tokens.css is the plain-CSS build). Retheming the elements needs no import: set --kai-color-* on :root. |
Also shipped: @kitn.ai/ui/elements/<name> for one element at a time, @kitn.ai/ui/autoloader to load each on demand on a static or CDN-served page with no bundler, @kitn.ai/ui/schemas for the generative-UI card schemas, and @kitn.ai/ui/provider for cards served from another origin. The authoritative list is the exports map in packages/ui/package.json.
The kit parses, your app fetches. There's no HTTP client, no key handling and no provider SDK in here: wire reads a stream you opened, which is why your keys never have to come near it.
They're fair, and mostly historic. Where they still bite, this kit answers them:
- React interop.
@kitn.ai/ui/reactis generated from the same build that generates the elements, so props are typed and events are React props. Norefplumbing to set an array. - SSR. Registration is client-only by construction, and the React wrappers register in an effect. CI renders every server entry in a DOM-free Node process on each run (
verify:ssr), and the Next.js and TanStack Start starters are server-rendered. - Types. The React wrappers are generated, not hand-maintained, and a custom-elements manifest ships for editor tag completion.
- Styling. Shadow DOM keeps your CSS out, which is also why you can't override a selector. Theming runs on
--kai-color-*custom properties, which cross the boundary by design, and elements expose CSSparts for the pieces worth reaching. - No build step. The elements load from a CDN in a plain
<script>tag, so they work on a host with no bundler to hook into.@kitn.ai/ui/autoloaderfetches each one on demand as its tag appears.
- Arrays and objects are set in JavaScript, not as HTML attributes.
messages,suggestions,modelsand the rest are assigned as properties. Only scalars (placeholder,loading,theme) work as attributes. - Events are non-bubbling
kai-*CustomEvents. Listen on the element itself, never a parent. Submit iskai-submitand the text isevent.detail.value. - Updating needs a new array and a new object for each item you changed. The new array reference is what tells the element something changed; the new item object is what makes the change visible. Adds, removes and reorders need only the fresh array — editing an existing item needs both, and mutating it in place renders nothing even inside a fresh array.
Every property, event and method for every element: docs/web-components.md.
The package ships a stdio MCP server, so an AI coding harness can build with this library instead of guessing at it:
claude mcp add kai -- npx -y @kitn.ai/ui mcpFour tools: component_reference (the real API for any kai-* element, generated from the build), scaffold (a working chat surface wired to your framework and backend), theme (brand it from a color or a description), debug (the classic mistakes). It runs locally and makes no network calls. Config for other harnesses: For AI agents.
Pre-1.0. Releases are cut by release-please from conventional commits with bump-minor-pre-major set, so a breaking change lands in a minor, not a major. Pin an exact version if that matters to you, and read the changelog before raising one.
Found a vulnerability? SECURITY.md has the private channel and what's in scope. Don't use the issue tracker for it.
pnpm + NX workspace, Node 22+.
packages/ui: the published kit (@kitn.ai/ui), its Storybook, and thekaiMCP.packages/create-kai: thenpm create kaiscaffolder.apps/docs: the Astro Starlight site behind ui.kitn.ai, consuming the kit viaworkspace:*.examples: a hand-composed starter per framework, plus static demos.create-kaicopies its templates fromexamples/starters.
pnpm install
pnpm dev # Storybook (6006) and the docs site (4321) together
pnpm build # every workspace, ui before docs
pnpm test # every workspace's tests
pnpm typecheckpnpm example:react and its siblings need packages/ui/dist/, which is gitignored, so run pnpm build:ui first.
CLAUDE.md is where the expensive knowledge lives: what a fresh clone needs before the test suite means anything, which caches lie, and which orderings are load-bearing. Read it before your first change.
MIT. See LICENSE.