Skip to content
kitn AI/UI

Solid

Solid apps get an extra path the other frameworks don’t: the components are authored in SolidJS, so you can import them straight into your JSX instead of going through the kai-* web components. Both paths ship in the package and mix freely:

  • Native SolidJS components (@kitn.ai/ui) — import ChatContainer, Message, PromptInput, and the rest directly into your JSX. Full compositional control, tree-shaken alongside your own code. Reach for this in a Solid app.
  • kai-* web components (@kitn.ai/ui/elements) — the drop-in shells when you want batteries included or need to share UI across frameworks.
Terminal window
npm install @kitn.ai/ui

solid-js is a peer dependency — install it if you haven’t already:

Terminal window
npm install solid-js

Import the design tokens once near your app entry:

import '@kitn.ai/ui/theme.css';

Compose directly in JSX. Size the layout with flex — ChatContainer fills its box, so give it a flex parent rather than a hard-coded viewport height.

import { createSignal, For } from 'solid-js';
import {
ChatConfig,
ChatContainer,
ChatContainerContent,
Message,
MessageContent,
PromptInput,
PromptInputTextarea,
PromptInputActions,
} from '@kitn.ai/ui';
import '@kitn.ai/ui/theme.css';
type ChatMessage = { id: string; role: 'user' | 'assistant'; content: string };
export function Chat() {
const [messages, setMessages] = createSignal<ChatMessage[]>([
{ id: '1', role: 'assistant', content: 'How can I help?' },
]);
const handleSubmit = async () => {
const userMsg: ChatMessage = {
id: crypto.randomUUID(),
role: 'user',
content: input(),
};
const history = [...messages(), userMsg];
setMessages(history);
setInput('');
const aid = crypto.randomUUID();
setMessages([...history, { id: aid, role: 'assistant', content: '' }]);
let answer = '';
for await (const token of streamFromYourAPI(history)) {
answer += token;
setMessages((prev) =>
prev.map((m) => (m.id === aid ? { ...m, content: answer } : m)),
);
}
};
const [input, setInput] = createSignal('');
return (
<div class="flex flex-col h-full">
<ChatConfig proseSize="sm">
<ChatContainer class="h-full">
<ChatContainerContent class="space-y-4 p-4">
<For each={messages()}>
{(msg) => (
<Message>
<MessageContent markdown>{msg.content}</MessageContent>
</Message>
)}
</For>
</ChatContainerContent>
</ChatContainer>
<PromptInput value={input()} onValueChange={setInput} onSubmit={handleSubmit}>
<PromptInputTextarea placeholder="Ask anything..." />
<PromptInputActions />
</PromptInput>
</ChatConfig>
</div>
);
}

ChatContainer is transport-agnostic — it owns the conversation UI, you own the request. Render your message list inside ChatContainerContent and stream the assistant reply into your signal in onSubmit.

The same building blocks compose into any layout. Use Resizable with ResizablePanel to add a draggable divider between panes — handles are inserted automatically between visible panels.

import { createSignal } from 'solid-js';
import { Resizable, ResizablePanel, ConversationList, ChatContainer, ChatContainerContent, PromptInput, PromptInputTextarea } from '@kitn.ai/ui';
function Workspace() {
const [activeId, setActiveId] = createSignal<string | undefined>(undefined);
return (
<div class="flex flex-col h-full">
<Resizable orientation="horizontal" onChange={(sizes) => persist(sizes)}>
<ResizablePanel defaultSize="25%" minSize="200px">
<ConversationList
conversations={conversations()}
activeId={activeId()}
onSelect={(id) => setActiveId(id)}
onNewChat={() => startNewConversation()}
/>
</ResizablePanel>
<ResizablePanel>
<ChatContainer class="h-full">
<ChatContainerContent class="p-4">
{/* message list */}
</ChatContainerContent>
</ChatContainer>
<PromptInput onSubmit={handleSubmit}>
<PromptInputTextarea placeholder="Ask anything..." />
</PromptInput>
</ResizablePanel>
</Resizable>
</div>
);
}

ResizablePanel accepts defaultSize (px or %) plus optional minSize / maxSize. onChange receives an array of percent sizes — persist it to restore the layout on reload.

Every feature component works standalone — drop them into any part of your UI without adopting a full chat shell.

import {
Markdown, Reasoning, ReasoningTrigger, ReasoningContent, Tool, Artifact,
} from '@kitn.ai/ui';
<Markdown content={assistantReply} />
<Reasoning isStreaming={isStreaming}>
<ReasoningTrigger>View reasoning</ReasoningTrigger>
<ReasoningContent markdown>{thinkingText}</ReasoningContent>
</Reasoning>
<Tool toolPart={toolCall} defaultOpen />
<Artifact files={artifactFiles} />

When you want a complete chat in one tag, register the kai-* elements and use <kai-chat>. In Solid, pass rich data with prop: (forces a DOM property assignment, not a stringified attribute) and listen for CustomEvents with Solid’s on: namespace.

import '@kitn.ai/ui/elements';
import { createSignal } from 'solid-js';
type Message = { id: string; role: 'user' | 'assistant'; content: string };
function Shell() {
const [messages, setMessages] = createSignal<Message[]>([
{ id: '1', role: 'assistant', content: 'Hello! How can I help?' },
]);
const handleSubmit = (e: CustomEvent<{ value: string }>) => {
const userMsg: Message = {
id: crypto.randomUUID(),
role: 'user',
content: e.detail.value,
};
setMessages((prev) => [...prev, userMsg]);
// …call your model, then append the assistant reply
};
return (
<div style={{ display: 'flex', 'flex-direction': 'column', height: '100dvh' }}>
<kai-chat
prop:messages={messages()}
on:kai-submit={handleSubmit}
style={{ flex: 1, 'min-height': 0 }}
/>
</div>
);
}
CategoryExports
ConfigChatConfig, useChatConfig
Chat layoutChatContainer, ChatContainerContent, ChatContainerScrollAnchor
MessagesMessage, MessageAvatar, MessageContent, MessageActions, MessageCopyButton
PromptPromptInput, PromptInputTextarea, PromptInputActions, PromptInputAction
ContentMarkdown, CodeBlock, Reasoning, Tool, Artifact
SidebarConversationList, ConversationItem
LayoutResizable, ResizablePanel
PrimitivesuseTextStream, useStickToBottom, useAutoResize, useVoiceRecorder
Native components (@kitn.ai/ui)Web components (@kitn.ai/ui/elements)
Recommended forSolid appsAny framework, or sharing UI across frameworks
BundleTree-shaken with your own codePre-built, self-contained
CompositionFull JSX controlSingle element, attribute-driven
SignalsNative createSignalprop: + on: bindings

Mix freely: native components on one screen, a <kai-chat> shell on another.

See Installation for bundler configuration and CDN options.