parseTree to O(n) and explain the tradeoff.find() with a pre-built Map so each node is visited once:const index = new Map(nodes.map(n => [n.id, n]));
for (const n of nodes) link(n, index.get(n.parent));@ai-sdk/react messages with <Conversation>— text, reasoning, tools & sources, mapped for you.See the drop-in →Stop rebuilding the same chat surface for every project. skeehn ships the full AI-interface layer — polished, accessible, and tested.
Bubbles for user, assistant, tool and system roles — grouped, timestamped, with hover actions.
Token-paced streaming text with a real caret and sticky auto-scroll — not cheap per-token flicker.
Collapsible thinking blocks and step-by-step reasoning traces with status and timing.
Tool cards with pending / running / success / error states, parameters and result output.
Source citation cards and copy-ready code blocks with line numbers and syntax tokens.
Agent status, typing indicators, voice sessions with a live waveform, and prompt suggestions.
A clean, neutral default ships in the box — then seven more skins take you anywhere, from green-phosphor Terminal to Brutalist, all from one token contract. The same exchange, three ways:
14 core · 15 AI · 3 layout, viz & motion bundles. Each ships as plain CSS with an optional React wrapper — add one or add them all.
Most AI kits hand you a black box that looks like everyone else’s. skeehn hands you the source.
Copy components into your repo with the CLI or shadcn registry — or just npm i. No black box, no lock-in, edit anything.
One token contract, eight built-in skins, or your own. Flip one data-attribute and the whole UI reskins — no rewrites.
15 components shaped for real AI UX — streaming, reasoning, tools, agents — on top of 14 core primitives.
Plain CSS with an optional React wrapper. Works with any framework, with server components, even without JS.
Ship today
Wire the useChat hook to your endpoint, drop in ChatBubble, and you have a streaming chat UI. Then theme it.
import { ChatBubble } from '@skeehn/react';
import { useChat } from '@skeehn/react/hooks';
export function Chat() {
const { messages } = useChat();
return messages.map((m) => (
<ChatBubble key={m.id} role={m.role}>{m.content}</ChatBubble>
));
}