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

@maxischmaxi/bootai-react

v0.2.0

Published

Drop-in AI chat widget for React apps and static sites. Ships as both a React component (`ChatBot`) and a standalone IIFE script (`widget.global.js`) for use without a build step.

Downloads

412

Readme

@maxischmaxi/bootai-react

Drop-in AI chat widget for React apps and static sites. Ships as both a React component (ChatBot) and a standalone IIFE script (widget.global.js) for use without a build step.

Installation

# npm
npm install @maxischmaxi/bootai-react

# pnpm
pnpm add @maxischmaxi/bootai-react

# yarn
yarn add @maxischmaxi/bootai-react

Peer dependencies: react >=18 and react-dom >=18.

Quick Start

React

import { ChatBot } from "@maxischmaxi/bootai-react";

function App() {
  return (
    <ChatBot
      siteId="your-site-id"
      apiUrl="https://api.example.com"
      consentGiven={true}
    />
  );
}

Script Tag (no build step)

<script src="https://cdn.example.com/widget.global.js"></script>
<script>
  BootAI.render({
    siteId: "your-site-id",
    apiUrl: "https://api.example.com",
    consentGiven: true,
  });
</script>

Props

ChatBotProps

| Prop | Type | Default | Description | |------|------|---------|-------------| | siteId | string | required | Unique identifier for your site. | | apiUrl | string | required | Base URL of the BootAI chat API. | | theme | "light" \| "dark" \| "auto" | "light" | Color theme. "auto" follows prefers-color-scheme. | | display | "floating" \| "inline" | "floating" | Floating shows a FAB + popup panel. Inline renders the chat directly in the DOM flow. | | consentGiven | boolean \| ConsentConfig | false | Controls whether the chat is active. See Consent. | | classNames | ChatBotClassNames | — | CSS class names per UI slot. See Custom Styling. | | styles | ChatBotStyles | — | Inline style overrides per UI slot. See Custom Styling. | | renderCallbacks | ChatBotRenderCallbacks | — | Replace any UI section with a custom render function. See Render Callbacks. |


Consent

The widget renders nothing until consent is granted. There are two ways to provide consent:

Simple boolean

Pass consentGiven={true} when your app already manages consent externally.

<ChatBot siteId="..." apiUrl="..." consentGiven={true} />

Consent config (built-in banner)

Pass a ConsentConfig object to show a built-in consent banner before the chat loads.

<ChatBot
  siteId="..."
  apiUrl="..."
  consentGiven={{
    text: "This chat is operated by a third party. By using it you agree to data processing.",
    privacyUrl: "https://example.com/privacy",
    acceptLabel: "Start Chat",        // optional, default: "Akzeptieren"
    storage: "localStorage",          // optional, default: "sessionStorage"
  }}
/>

ConsentConfig

| Field | Type | Default | Description | |-------|------|---------|-------------| | text | string | required | Consent banner message. | | privacyUrl | string | required | Link to your privacy policy. | | acceptLabel | string | "Akzeptieren" | Label for the accept button. | | storage | "localStorage" \| "sessionStorage" \| "none" | "sessionStorage" | Where to persist the consent decision. "none" means the banner appears on every visit. |


Display Modes

Floating (default)

A floating action button (FAB) in the bottom-right corner. Clicking it opens/closes the chat panel as a popup.

<ChatBot siteId="..." apiUrl="..." display="floating" consentGiven={true} />

Inline

The chat panel renders directly inside the parent element, like a regular component. No FAB, no popup.

<ChatBot siteId="..." apiUrl="..." display="inline" consentGiven={true} />

Theming

Three theme modes are available:

| Value | Behavior | |-------|----------| | "light" | Light color scheme (white background, dark text). | | "dark" | Dark color scheme (dark background, light text). | | "auto" | Follows the user's system preference via prefers-color-scheme. |

The active theme is exposed on the root element as data-bootai-theme.

Colors Object

Both themes resolve to a Colors object that is passed to all render callbacks:

interface Colors {
  bg: string;          // main background
  surface: string;     // secondary surface (e.g. input area)
  border: string;      // border color
  text: string;        // primary text color
  muted: string;       // secondary text color
  userBg: string;      // user message bubble background
  assistantBg: string; // assistant message bubble background
  inputBg: string;     // input field background
  inputBorder: string; // input field border
}

Custom Styling

Every UI element in the widget is identified by a slot key. You can target any slot with CSS class names, inline style overrides, or both.

Slot Keys

| Slot | Element | |------|---------| | consentBanner | Consent banner container | | consentText | Consent text paragraph | | consentPrivacyLink | Privacy policy link | | consentAcceptButton | Accept/start button | | floatingRoot | Floating mode root (fixed position container) | | floatingPanel | Floating chat panel (popup) | | fab | Floating action button | | fabIcon | Icon inside the FAB | | inlineRoot | Inline mode root container | | header | Chat header bar | | headerStatusDot | Green status indicator dot | | headerTitle | Header title text | | headerSubtitle | Header subtitle text | | messagesContainer | Scrollable messages area | | emptyState | "How can I help?" placeholder | | userMessage | User message bubble | | assistantMessage | Assistant message bubble | | loadingIndicator | Typing indicator (dots) | | inputContainer | Input area wrapper | | input | Text input field | | sendButton | Send button |

classNames — CSS Classes

Add CSS class names to any slot. Useful for Tailwind CSS, CSS Modules, or plain stylesheets.

<ChatBot
  siteId="..."
  apiUrl="..."
  consentGiven={true}
  classNames={{
    header: "my-chat-header",
    userMessage: "my-user-bubble",
    sendButton: "my-send-btn",
  }}
/>

styles — Inline Style Overrides

Override default inline styles per slot. Your styles are merged on top of the defaults (spread after), so they take precedence.

<ChatBot
  siteId="..."
  apiUrl="..."
  consentGiven={true}
  styles={{
    header: { background: "#92400e" },
    headerStatusDot: { backgroundColor: "#fbbf24" },
    sendButton: { background: "#92400e" },
    inlineRoot: { borderColor: "#d97706", maxHeight: 600 },
  }}
/>

Combining classNames and styles

Both can be used simultaneously on the same slot. The className is applied to the element, and styles are merged into the style attribute.

<ChatBot
  siteId="..."
  apiUrl="..."
  consentGiven={true}
  classNames={{ header: "shadow-lg rounded-t-xl" }}
  styles={{ header: { background: "linear-gradient(135deg, #667eea, #764ba2)" } }}
/>

Render Callbacks

For full control over a UI section, provide a render callback. When a render callback is set, it completely replaces the default rendering for that section. Each callback receives contextual props and must return ReactNode.

ChatBotRenderCallbacks

interface ChatBotRenderCallbacks {
  renderConsentBanner?: (props: ConsentBannerRenderProps) => ReactNode;
  renderHeader?:           (props: HeaderRenderProps) => ReactNode;
  renderMessage?:          (props: MessageRenderProps) => ReactNode;
  renderEmptyState?:       (props: EmptyStateRenderProps) => ReactNode;
  renderLoadingIndicator?: (props: LoadingIndicatorRenderProps) => ReactNode;
  renderInput?:            (props: InputRenderProps) => ReactNode;
  renderFab?:              (props: FabRenderProps) => ReactNode;
}

Callback Props Reference

ConsentBannerRenderProps

| Prop | Type | Description | |------|------|-------------| | isDark | boolean | Whether dark mode is active. | | display | "floating" \| "inline" | Current display mode. | | text | string | Consent text from config. | | privacyUrl | string | Privacy policy URL. | | acceptLabel | string | Accept button label. | | onAccept | () => void | Must be called to grant consent and persist the decision. |

HeaderRenderProps

| Prop | Type | Description | |------|------|-------------| | colors | Colors | Active theme colors. |

MessageRenderProps

| Prop | Type | Description | |------|------|-------------| | message | Message | The message object ({ role, content }). | | colors | Colors | Active theme colors. | | renderedContent | ReactNode | Pre-rendered content with parsed links. Use this to keep default link rendering. |

EmptyStateRenderProps

| Prop | Type | Description | |------|------|-------------| | colors | Colors | Active theme colors. |

LoadingIndicatorRenderProps

| Prop | Type | Description | |------|------|-------------| | colors | Colors | Active theme colors. |

InputRenderProps

| Prop | Type | Description | |------|------|-------------| | value | string | Current input value. | | isLoading | boolean | Whether a response is streaming. | | colors | Colors | Active theme colors. | | onChange | (value: string) => void | Update the input value. | | onSend | () => void | Send the current message. | | inputRef | RefObject<HTMLInputElement \| null> | Ref for auto-focus behavior. Attach to your <input> element. |

FabRenderProps

| Prop | Type | Description | |------|------|-------------| | isOpen | boolean | Whether the chat panel is open. | | onToggle | () => void | Toggle the chat panel. |

Examples

Custom header with branding

<ChatBot
  siteId="..."
  apiUrl="..."
  consentGiven={true}
  renderCallbacks={{
    renderHeader: ({ colors }) => (
      <div style={{ padding: "16px 20px", background: "#92400e", color: "#fff", display: "flex", alignItems: "center", gap: 10 }}>
        <img src="/logo.svg" alt="" width={24} height={24} />
        <div>
          <div style={{ fontWeight: 600, fontSize: 14 }}>My Store Support</div>
          <div style={{ fontSize: 12, opacity: 0.85 }}>We typically reply instantly</div>
        </div>
      </div>
    ),
  }}
/>

Custom empty state

renderCallbacks={{
  renderEmptyState: ({ colors }) => (
    <div style={{ padding: 40, textAlign: "center", color: colors.muted }}>
      <p style={{ fontSize: 24 }}>👋</p>
      <p>Ask me anything about our products!</p>
    </div>
  ),
}}

Custom message rendering

renderCallbacks={{
  renderMessage: ({ message, colors, renderedContent }) => (
    <div style={{
      display: "flex",
      justifyContent: message.role === "user" ? "flex-end" : "flex-start",
    }}>
      {message.role === "assistant" && <img src="/bot-avatar.png" width={28} height={28} />}
      <div style={{
        padding: "10px 14px",
        borderRadius: 12,
        background: message.role === "user" ? colors.userBg : colors.assistantBg,
        color: message.role === "user" ? "#fff" : colors.text,
        maxWidth: "75%",
      }}>
        {renderedContent}
      </div>
    </div>
  ),
}}

Custom FAB

renderCallbacks={{
  renderFab: ({ isOpen, onToggle }) => (
    <button
      onClick={onToggle}
      style={{
        width: 60, height: 60, borderRadius: "50%",
        background: isOpen ? "#ef4444" : "#10b981",
        color: "#fff", border: "none", cursor: "pointer",
        fontSize: 24,
      }}
    >
      {isOpen ? "✕" : "💬"}
    </button>
  ),
}}

Widget Script (IIFE)

The package also ships a standalone script at dist/widget.global.js that bundles React internally. Use it on any HTML page without a build step.

Loading

<script src="https://cdn.example.com/widget.global.js"></script>

This exposes a global BootAI object with two methods:

BootAI.render(options)

Renders the chat widget. Accepts all the same options as the React component props.

BootAI.render({
  siteId: "your-site-id",
  apiUrl: "https://api.example.com",
  theme: "auto",
  display: "floating",
  consentGiven: {
    text: "We use a chat assistant powered by AI.",
    privacyUrl: "/privacy",
    acceptLabel: "Got it",
    storage: "localStorage",
  },
  classNames: {
    header: "my-header-class",
  },
  styles: {
    header: { background: "#1e3a5f" },
    sendButton: { background: "#1e3a5f" },
  },
});

BootAI.destroy()

Unmounts the widget and removes its DOM container.

BootAI.destroy();

Render Callbacks in the Widget Script

Since widget users don't have React, render callbacks return HTMLElement or string (HTML) instead of ReactNode. The widget internally bridges these to React using an HtmlBridge component.

BootAI.render({
  siteId: "...",
  apiUrl: "...",
  consentGiven: true,
  renderCallbacks: {
    // Return an HTML string
    renderHeader: function (props) {
      return '<div style="padding:16px 20px;background:#1e3a5f;color:#fff">' +
        '<b>My Custom Header</b></div>';
    },

    // Or return a DOM element
    renderEmptyState: function (props) {
      var div = document.createElement("div");
      div.style.padding = "40px";
      div.style.textAlign = "center";
      div.style.color = props.colors.muted;
      div.textContent = "How can we help you today?";
      return div;
    },
  },
});

The callback props are identical to the React version. The only difference is the return type: HTMLElement | string instead of ReactNode.


Web Component

The package ships a custom element at dist/web-component.global.js that bundles React internally. Use it on any HTML page with a single <script> tag — no build step, no JS required.

Loading

<script src="https://cdn.example.com/web-component.global.js"></script>

This registers the <bootai-chat> custom element.

HTML Attributes

Simple string/boolean values are set via HTML attributes:

| Attribute | Type | Description | |-----------|------|-------------| | site-id | string | Required. Your site identifier. | | api-url | string | Base URL of the BootAI chat API. | | theme | "light" \| "dark" \| "auto" | Color theme. Default: "light". | | display | "floating" \| "inline" | Display mode. Default: "floating". | | consent-given | "true" | Set to "true" to skip the consent banner. |

JS Properties

For complex data (objects, callbacks), set properties via JavaScript:

| Property | Type | Description | |----------|------|-------------| | consentGiven | boolean \| ConsentConfig | Consent config object or boolean. | | classNames | ChatBotClassNames | CSS class overrides per slot. | | styles | ChatBotStyles | Inline style overrides per slot. | | renderCallbacks | WidgetRenderCallbacks | Custom render functions (return HTMLElement \| string). |

Example

<script src="https://cdn.example.com/web-component.global.js"></script>

<!-- Simple usage with HTML attributes -->
<bootai-chat
  site-id="your-site-id"
  api-url="https://api.example.com"
  theme="auto"
  consent-given="true"
></bootai-chat>

<!-- Advanced usage with JS properties -->
<bootai-chat
  site-id="your-site-id"
  api-url="https://api.example.com"
  display="inline"
></bootai-chat>

<script>
  const chat = document.querySelector("bootai-chat");
  chat.consentGiven = {
    text: "This chat uses AI. Do you agree?",
    privacyUrl: "/privacy",
    acceptLabel: "Accept",
    storage: "localStorage",
  };
  chat.styles = {
    header: { background: "#1e3a5f" },
    sendButton: { background: "#1e3a5f" },
  };
</script>

TypeScript

All types are exported from the package entry point:

import type {
  ChatBotProps,
  ConsentConfig,
  ChatBotSlot,
  ChatBotClassNames,
  ChatBotStyles,
  ChatBotRenderCallbacks,
  Message,
  Colors,
  ConsentBannerRenderProps,
  HeaderRenderProps,
  MessageRenderProps,
  EmptyStateRenderProps,
  LoadingIndicatorRenderProps,
  InputRenderProps,
  FabRenderProps,
} from "@maxischmaxi/bootai-react";

Key Types

// All targetable UI slots
type ChatBotSlot =
  | "consentBanner" | "consentText" | "consentPrivacyLink" | "consentAcceptButton"
  | "floatingRoot" | "floatingPanel" | "fab" | "fabIcon"
  | "inlineRoot"
  | "header" | "headerStatusDot" | "headerTitle" | "headerSubtitle"
  | "messagesContainer" | "emptyState" | "userMessage" | "assistantMessage" | "loadingIndicator"
  | "inputContainer" | "input" | "sendButton";

// CSS class name overrides
type ChatBotClassNames = Partial<Record<ChatBotSlot, string>>;

// Inline style overrides
type ChatBotStyles = Partial<Record<ChatBotSlot, React.CSSProperties>>;

// Chat message
interface Message {
  role: "user" | "assistant";
  content: string;
}

How It Works

  1. Consent gate — The widget renders nothing until consentGiven is truthy. If a ConsentConfig is passed, a banner is shown first.
  2. Readiness check — After consent, the widget calls GET {apiUrl}/chat/ready?siteId={siteId}. It only renders the chat UI when the API responds { ready: true }.
  3. Streaming responses — Messages are sent via POST {apiUrl}/chat and responses are streamed using ReadableStream. The assistant's reply appears token by token.
  4. Link detection — Assistant messages automatically render Markdown-style links ([text](url)) and bare URLs as clickable <a> tags.
  5. Hydration-safe — The widget defers rendering until after client-side mount to prevent hydration mismatches in SSR frameworks (Next.js, Remix, etc.).

API Contract

GET /chat/ready?siteId={siteId}

Returns { ready: boolean }. The widget polls this once after consent.

POST /chat

Request body:

{
  "siteId": "your-site-id",
  "message": "user's message",
  "history": [
    { "role": "user", "content": "previous message" },
    { "role": "assistant", "content": "previous response" }
  ]
}

Response: streaming text body. An internal ---SOURCES_END---\n marker separates metadata from the visible response.


Build Outputs

| File | Format | Description | |------|--------|-------------| | dist/index.js | ESM | React component (tree-shakeable). react and react-dom are external. | | dist/index.cjs | CJS | React component for CommonJS environments. | | dist/index.d.ts | TypeScript | Type declarations. | | dist/widget.global.js | IIFE | Standalone script with React bundled. Exposes BootAI global. | | dist/web-component.global.js | IIFE | Custom element <bootai-chat> with React bundled. |


License

See repository root for license information.