npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kitn.ai/chat

v0.14.1

Published

Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.

Downloads

696

Readme

@kitn.ai/chat

Framework-agnostic web components for building AI chat interfaces — message threads, prompt inputs, streaming responses, markdown + code rendering, reasoning/tool panels, attachments, and a conversation sidebar. Drop them into any app: React, Vue, Angular, Svelte, or plain HTML.

It can be consumed two ways:

  1. As framework-agnostic web components (primary) — drop <kc-chat>, <kc-conversations>, and <kc-prompt-input> into any project (React, Vue, Angular, Svelte, plain HTML). Each is fully style-isolated via Shadow DOM, and the rendering runtime is bundled in, so the host needs nothing.
  2. As native SolidJS components — the kit is authored in SolidJS, so SolidJS apps can import the components directly for full compositional control. (This is a convenience for SolidJS users, not a requirement — the web components work everywhere.)

Highlights

  • ~50 composable components across three layers: headless primitives → accessible UI primitives (built in-house, WCAG 2.1 AA — no third-party UI dependency) → AI feature components.
  • Shadow-DOM web components — zero CSS conflicts in any host. The host's styles can't leak in; the kit's Tailwind can't leak out.
  • Lightweight by design — a markdown-only <kc-chat> is ~110 KB gzip (one file). Syntax highlighting (Shiki) is loaded on demand, per-language, with no WASM — and never loads at all if you don't render code.
  • Tailwind v4 design tokens — rebrand by overriding --color-* custom properties.

Install

npm install @kitn.ai/chat

SolidJS consumers also need solid-js (a peer dependency):

npm install solid-js

Quick start

Option A — Web components (any framework / plain HTML)

Build the bundle, then import it as a side-effect (it registers the custom elements):

npm run build   # emits dist/kitn-chat.es.js
<body style="height: 100vh; margin: 0;">
  <kc-chat style="display:block; height:100%;"></kc-chat>

  <script type="module">
    import '@kitn.ai/chat/elements';

    const chat = document.querySelector('kc-chat');

    // Rich data is set as JS properties (not HTML attributes)
    chat.messages = [
      { id: '1', role: 'assistant', content: 'Hello! How can I help?' },
    ];

    // Events are CustomEvents dispatched on the element (they do not bubble)
    chat.addEventListener('submit', (e) => {
      console.log('user sent:', e.detail.value);
    });
  </script>
</body>

The element bundle is ES-module only and loads via <script type="module"> in every modern browser. See docs/web-components.md for the full element API (every property, event, and the ChatMessage schema).

Or load from a CDN (no build, no npm)

The element bundle is a self-contained ES module — load it directly from jsDelivr or unpkg, no install or bundler required:

<script type="module">
  import 'https://cdn.jsdelivr.net/npm/@kitn.ai/chat/dist/kitn-chat.es.js';
  // …or unpkg: import 'https://unpkg.com/@kitn.ai/chat/dist/kitn-chat.es.js';
</script>

<kc-chat></kc-chat>

The URLs above track the latest release — handy for trying things out. For production, pin an exact version (e.g. @kitn.ai/[email protected]/dist/kitn-chat.es.js): pinned URLs are immutable and cached far more aggressively, and — since this package is pre-1.0 — pinning shields you from breaking changes in a future minor release. SolidJS and the kit's CSS are bundled in, and the lazy code-highlighting chunks load from the same CDN on demand. To override design tokens, also include theme.css:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@kitn.ai/chat/theme.css">

Option B — SolidJS components

import {
  ChatConfig, ChatContainer, ChatContainerContent,
  Message, MessageContent,
  PromptInput, PromptInputTextarea, PromptInputActions,
} from '@kitn.ai/chat';
import '@kitn.ai/chat/theme.css';

function App() {
  const [input, setInput] = createSignal('');
  return (
    <ChatConfig proseSize="sm">
      <ChatContainer class="h-full">
        <ChatContainerContent class="space-y-4 p-4">
          <Message>
            <MessageContent markdown>{`## Hi\n\nAsk me anything.`}</MessageContent>
          </Message>
        </ChatContainerContent>
      </ChatContainer>
      <PromptInput value={input()} onValueChange={setInput} onSubmit={() => setInput('')}>
        <PromptInputTextarea placeholder="Ask anything..." />
        <PromptInputActions>{/* your buttons */}</PromptInputActions>
      </PromptInput>
    </ChatConfig>
  );
}

The SolidJS entry (.) is the kit's raw source (src/index.ts) — your bundler compiles it, so it tree-shakes to just what you import.

Integrations

The components are deliberately transport-agnostic: <kc-chat> just renders the messages array you give it and emits a submit event when the user sends. You own the request, the streaming, and any extras like text-to-speech. The patterns below use the web component, but the same wiring applies to the SolidJS API.

Streaming responses from OpenRouter

OpenRouter exposes an OpenAI-compatible streaming API (Server-Sent Events). On submit, append the user message + an empty assistant message, then grow the assistant message as tokens arrive.

Security: never ship your API key to the browser. In production, point fetch at your own backend endpoint that proxies to OpenRouter and injects the key. The parsing below is identical either way.

<kc-chat id="chat" style="display:block; height:100vh;"></kc-chat>

<script type="module">
  import '@kitn.ai/chat/elements';

  const chat = document.getElementById('chat');
  chat.messages = [];

  chat.addEventListener('submit', async (e) => {
    const text = e.detail.value.trim();
    if (!text) return;

    // 1. Show the user message immediately
    const history = [...chat.messages, { id: crypto.randomUUID(), role: 'user', content: text }];
    chat.messages = history;
    chat.value = '';        // clear the input
    chat.loading = true;

    // 2. Add an empty assistant message we'll stream into
    const assistantId = crypto.randomUUID();
    chat.messages = [...history, { id: assistantId, role: 'assistant', content: '' }];

    try {
      // In production, replace this URL with your own proxy endpoint.
      const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${OPENROUTER_API_KEY}`, // server-side in production!
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          model: 'anthropic/claude-sonnet-4',
          stream: true,
          messages: history.map((m) => ({ role: m.role, content: m.content })),
        }),
      });

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = '';
      let answer = '';

      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });

        // SSE frames are separated by newlines; each data line is JSON
        const lines = buffer.split('\n');
        buffer = lines.pop();               // keep the partial last line
        for (const line of lines) {
          const s = line.trim();
          if (!s.startsWith('data:')) continue;  // skip ": keep-alive" comments
          const payload = s.slice(5).trim();
          if (payload === '[DONE]') continue;
          try {
            const delta = JSON.parse(payload).choices?.[0]?.delta?.content;
            if (!delta) continue;
            answer += delta;
            // Replace the assistant message with a NEW object so the row re-renders
            chat.messages = chat.messages.map((m) =>
              m.id === assistantId ? { ...m, content: answer } : m
            );
          } catch { /* ignore non-JSON keep-alive lines */ }
        }
      }
    } catch (err) {
      chat.messages = chat.messages.map((m) =>
        m.id === assistantId ? { ...m, content: '⚠️ ' + err.message } : m
      );
    } finally {
      chat.loading = false;
    }
  });
</script>

Key point: reassign chat.messages with a new array containing a new object for the streaming message on each chunk — that's what triggers the re-render. Mutating the existing object in place won't update the view.

Text-to-speech (TTS)

Option 1 — Browser-native (zero dependencies)

The Web Speech API speaks text with no network call. Speak each assistant reply once it finishes streaming — call speak(answer) right before chat.loading = false in the example above:

function speak(text) {
  if (!('speechSynthesis' in window)) return;
  const utter = new SpeechSynthesisUtterance(text);
  utter.lang = 'en-US';
  utter.rate = 1;
  speechSynthesis.cancel();   // stop anything already playing
  speechSynthesis.speak(utter);
}

To speak as it streams, flush complete sentences instead of waiting for the end:

let spokenUpTo = 0;
function speakIncremental(fullText) {
  const lastBreak = fullText.lastIndexOf('. ', fullText.length);
  if (lastBreak > spokenUpTo) {
    speak(fullText.slice(spokenUpTo, lastBreak + 1));
    spokenUpTo = lastBreak + 1;
  }
}
// call speakIncremental(answer) inside the streaming loop

Option 2 — Cloud TTS (OpenAI, ElevenLabs, …)

For higher-quality voices, have your backend call a TTS API and return audio, then play it. Keep the provider key on the server.

async function speakCloud(text) {
  const res = await fetch('/api/tts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ text, voice: 'alloy' }),
  });
  const audio = new Audio(URL.createObjectURL(await res.blob()));
  audio.play();
}
// Example backend (Node) — /api/tts proxying OpenAI's speech endpoint
// const r = await openai.audio.speech.create({ model: 'gpt-4o-mini-tts', voice, input: text });
// res.setHeader('Content-Type', 'audio/mpeg');
// Readable.fromWeb(r.body).pipe(res);

You can trigger either option from the streaming completion (auto-read replies) or from a button you render alongside each message.

Speech-to-text (the other direction) is already built in — the kit ships a VoiceInput component for capturing microphone input. See Storybook (npm run dev).

Code highlighting (optional, on-demand)

Syntax highlighting uses Shiki and is wired to be as light as possible:

  • Nothing loads until a fenced code block actually renders.
  • Only the core, the JavaScript regex engine (no WASM), the one theme, and the one language grammar needed are fetched — each a small lazy chunk.
  • Default languages: javascript/js, typescript/ts, tsx, json, bash/sh. Add more or turn it off:
import { configureCodeHighlighting } from '@kitn.ai/chat/elements'; // or '@kitn.ai/chat'

configureCodeHighlighting({
  languages: { python: () => import('@shikijs/langs/python') },
});

// or disable entirely — no Shiki ever loads:
configureCodeHighlighting({ enabled: false });

Per element: <kc-chat codeHighlight={false}> renders code as plain text.

Theming

Visual appearance is driven by --color-* CSS custom properties in theme.css. Because inherited CSS pierces the Shadow DOM boundary, overriding tokens on :root rebrands the components — even the web-component ones:

:root {
  --color-background: #0f0f0f;
  --color-primary: #7c3aed;
  --color-muted: #1e1e1e;
}

For SolidJS usage, import @kitn.ai/chat/theme.css once. For web components the kit's CSS is injected into each shadow root automatically; only theme.css (design tokens) is optional to include.

For AI agents / LLMs

The package ships llmstxt.org-style files so coding agents (Claude Code, Copilot, Cursor, Codex) can wire up the components correctly:

  • llms.txt — dense orientation: install, the property-vs-attribute rule, the two-layer architecture, theming, and framework wiring.
  • llms-full.txt — the above plus a generated props/events reference for every kitn-* element, a streaming recipe, and a build runbook.

Both are auto-generated from dist/custom-elements.json during npm run build (so they never drift) and are published in the npm package — find them at node_modules/@kitn.ai/chat/llms.txt after install.

#1 thing agents get wrong: array/object data (messages, models, context, …) must be set as JS properties, not HTML attributes. Only scalars (placeholder, loading, theme) work as attributes.

Development

npm install          # install dependencies
npm run dev          # Storybook dev server at http://localhost:6006 (component playground)
npm test             # run the test suite (Vitest: jsdom unit tests + Storybook browser tests)
npm run build        # build the web-component bundle into dist/
npm run build-storybook   # static Storybook build

Storybook is the primary way to explore and develop components in isolation.

Project structure

src/
  primitives/    Headless logic hooks + ChatConfig + on-demand highlighter
  ui/            Accessible UI primitives (Button, Dropdown, Tooltip, HoverCard, … built in-house, no third-party UI deps)
  components/    AI feature components (Message, PromptInput, Markdown, Tool, …)
  elements/      Web-component facades + defineKitnElement wrapper + Vite lib entry
  stories/       Composed example stories (full chat app, layouts)
theme.css        Design tokens (--color-*), animations, markdown styles
docs/
  web-components.md   Full web-component API reference

The web-component layer wraps a few coarse facades over the SolidJS components; the SolidJS API stays the source of truth and is unchanged by it.

Examples

A set of runnable examples and a hosted component playground are included in the repo. See examples/README.md for full details.

Composable showcase

The composable showcase demonstrates every individual element in one page. Build the package first, then serve from the repo root:

npm run build     # produces dist/kitn-chat.es.js
npm run examples  # static server at http://localhost:8000

Then open: http://localhost:8000/examples/composable/index.html

Storybook

Storybook is the primary component playground for development:

npm run dev    # dev server at http://localhost:6006

The published docs are deployed to GitHub Pages: https://kitn-ai.github.io/chat/

Framework example apps

The examples/react, examples/solid, examples/angular, and examples/vue directories are full Vite apps — install their dependencies and run the local dev server:

cd examples/react && npm install && npm run dev
# or
cd examples/solid && npm install && npm run dev
# or
cd examples/angular && npm install && npm run dev
# or
cd examples/vue && npm install && npm run dev
  • examples/react — uses the generated React wrappers from @kitn.ai/chat/react
  • examples/solid — uses the raw SolidJS component API
  • examples/angular — uses the web components natively via Angular's [prop] / (event) bindings with CUSTOM_ELEMENTS_SCHEMA (no wrappers needed)
  • examples/vue — uses the web components natively via Vue's :prop.prop modifier and @event bindings; isCustomElement in vite.config.ts prevents Vue treating kitn-* tags as Vue components

Docs and reference

Bundle size

| Scenario | Loaded | |---|--:| | <kc-chat>, markdown only (no code blocks) | ~110 KB gzip (~413 KB raw), one file | | + a code block | adds Shiki core + JS engine + that language + theme, lazily | | Highlighting disabled | Shiki never loads |

The build is ES-module only — a UMD/IIFE build can't code-split and would inline every lazy chunk into one multi-MB file, so it's intentionally omitted.