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

@michitson/react-chat

v0.0.3

Published

Lightweight, single-file React chat UI primitive — streaming, markdown, assistant-driven choices, AbortSignal. Bring your own backend.

Readme

@michitson/react-chat

A lightweight, single-file React chat UI primitive. Streaming, markdown, AbortSignal cancellation, and assistant-driven choice buttons. Bring your own backend.

This is not a chat framework. It's a UI component for projects that want a polished chat interface without committing to a multi-package framework. Perfect for embedded help bots, doc assistants, support widgets, internal tools, and anywhere the "chat" is a feature inside a larger app rather than the whole app.

Install

npm install @michitson/react-chat
# or pnpm / yarn

Peer dependencies: react ^18 || ^19, react-dom ^18 || ^19.

You also need Tailwind CSS configured in your project, and you must include this package in your Tailwind content array:

// tailwind.config.ts
export default {
  content: [
    './src/**/*.{ts,tsx}',
    './node_modules/@michitson/react-chat/dist/**/*.{js,mjs,cjs}',
  ],
  darkMode: 'class',
  // ...
};

For syntax-highlighted code blocks in assistant messages, import a highlight.js theme once anywhere in your app:

import 'highlight.js/styles/github-dark.css';

Quickstart

import { Chatbot, type SendMessage } from '@michitson/react-chat';

const echo: SendMessage = async function* (messages) {
  const last = messages[messages.length - 1].content;
  for (const word of `You said: ${last}`.split(' ')) {
    await new Promise((r) => setTimeout(r, 50));
    yield word + ' ';
  }
};

export default function Page() {
  return (
    <div className="h-screen">
      <Chatbot sendMessage={echo} />
    </div>
  );
}

The component fills its parent — give it a sized container.

Streaming protocol

Your sendMessage function returns an AsyncIterable<ChatStreamChunk>:

type ChatStreamChunk =
  | string                                           // append text to the assistant message
  | { type: 'choices'; options: string[] };          // attach clickable choice buttons

Yield string chunks to stream text into the current assistant bubble. Yield a choices chunk to attach enumerated buttons to the assistant message — the user clicking one becomes their next user message, and the buttons disappear as soon as the next turn begins.

A real backend hitting an LLM via Server-Sent Events:

const send: SendMessage = async function* (messages, { signal }) {
  const res = await fetch('/api/chat', {
    method: 'POST',
    body: JSON.stringify({ messages }),
    signal,
  });
  const reader = res.body!.getReader();
  const decoder = new TextDecoder();
  while (true) {
    const { value, done } = await reader.read();
    if (done) return;
    yield decoder.decode(value);
  }
};

The component plumbs an AbortSignal to your backend automatically. When the user clicks the Stop button, signal.aborted becomes true and fetch (or your SDK) cancels.

API

interface ChatbotProps {
  sendMessage: SendMessage;
  initialMessages?: ChatMessage[];
  placeholder?: string;
  density?: 'comfortable' | 'compact' | 'tight';
  className?: string;
  classNames?: {
    container?: string;
    messageList?: string;
    userBubble?: string;
    assistantBubble?: string;
    input?: string;
    sendButton?: string;
    choiceButton?: string;
  };
}

initialMessages

Seed the conversation with prior messages. Useful for a welcome from the assistant — including initial choice buttons:

const welcome = [
  {
    id: 'welcome',
    role: 'assistant' as const,
    content: 'Hi! What would you like to do?',
    choices: ['Tell me a joke', 'Show me docs'],
  },
];

<Chatbot sendMessage={send} initialMessages={welcome} />

density

Controls horizontal spacing between user and assistant bubbles:

  • comfortable (default) — bubbles can be wide, sit on opposite sides
  • compact — bubbles narrower, gentler indent
  • tight — bubbles narrowest, deep indent so opposite roles can interleave visually

className and classNames

className applies to the outer container. classNames is a slot map for per-element overrides:

<Chatbot
  sendMessage={send}
  className="rounded-xl border"
  classNames={{
    userBubble: 'bg-blue-600',
    assistantBubble: 'bg-gray-100',
    sendButton: 'bg-blue-600 hover:bg-blue-700',
  }}
/>

Slot classes are merged with the defaults via simple string concatenation, so later (yours) wins for conflicting Tailwind utilities.

Dark mode

Toggle the dark class on <html> (or any ancestor) and the component restyles. Standard Tailwind darkMode: 'class' setup.

What's included

  • Multi-turn conversation history with smooth scrolling
  • Streaming token rendering (no flash on each chunk)
  • Smart auto-scroll: pauses when the user scrolls up, re-engages when they scroll back to bottom
  • Markdown rendering via react-markdown + remark-gfm for tables/strikethrough/etc + rehype-highlight for syntax highlighting
  • Auto-grow textarea (max ~200px) with Enter-sends / Shift+Enter-newline
  • Disabled input + Stop button while streaming
  • Three role-styled bubble densities
  • Dark mode out of the box
  • Real AbortController cancellation
  • Assistant-driven choice buttons (auto-hide on next turn)
  • TypeScript-first, no any in the public surface

What's deliberately excluded

These are real product decisions, not omissions to be filled in later:

  • Tool-call rendering as UI, branching, edit-last, attachments, multi-thread management, voice. If you need these, look at @assistant-ui/react — it's a real chat framework and ships them well. This package stays small on purpose.

License

MIT © michitson