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

@leadbot/components

v2.26.0

Published

LeadBot Components: mount agent chat, forms, callback/email requests and WhatsApp handoff into your own DOM.

Readme

@leadbot/components

LeadBot Components building blocks: mount an AI agent chat, lead forms, callback/email requests or a WhatsApp handoff into your own DOM, styled by your site. Rendered into the light DOM (no shadow DOM, no iframe); Preact is bundled internally and invisible to consumers.

Usage

import {
  init,
  mountAgentChat,
  mountForm,
  mountCallbackRequest,
  mountEmailRequest,
  mountWhatsApp,
  mountButtons,
  mountFlow,
  openWidget,
} from "@leadbot/components";
import "@leadbot/components/styles.css";

init({ domainKey: "your-domain-key" });

const chat = mountAgentChat(document.querySelector("#chat")!, {
  leadbotId: 18,
  id: "chat-step-uuid",
  initialMessage: "Hi! How can I help?",
  conversationStarters: ["What are your opening hours?"],
});

const form = mountCallbackRequest(document.querySelector("#callback")!, {
  leadbotId: 19,
  id: "callback-step-uuid",
  eventLabel: "Homepage callback",
  marksAsQualified: true,
});

// Every mount returns { update(options), destroy() }.
form.update({ submitLabel: "Call me!" });
chat.destroy();

const buttons = mountButtons(document.querySelector("#buttons")!, {
  leadbotId: 18,
  id: "buttons-step-uuid",
  onSelect: (button) => console.log("clicked", button.label),
});

const flow = mountFlow(document.querySelector("#flow")!);

Every mount requires a leadbotId — the id of the component leadbot the component belongs to (each embed can target a different one) — and an id, the uuid of the published step it renders. Mounts also accept domainKey / apiHost to override the init() defaults, a className for the container, and a labels object to override individual UI strings. Defaults are not English: every component renders its own copy in the language of the bot as published, which arrives with the boot fetch — the page's <html lang> is only the fallback until it does.

preview / previewToken exist for the dashboard preview of unpublished draft flows: preview keeps submissions out of the inbox, previewToken authorizes the draft. The token is ignored unless preview is set, and it must never be committed into a production build — anyone reading the shipped JS could then read your unpublished flows.

Without a bundler (script tag / CDN)

One script tag and one container div — no npm, no build step. The bundle boots from its own script tag, injects the stylesheet, and mounts every [data-leadbot-step] container; the component to render comes from the published step's own type.

<script
  src="https://cdn.leadbot.com/components.js"
  data-leadbot-domain-key="your-domain-key"
  data-leadbot-api-host="https://collector.example.com"
  data-leadbot-id="18"
></script>

<div data-leadbot-step="chat-step-uuid"></div>

The full API is on window.LeadBot (init, every mount*, mountAll, openWidget). Container attributes and the rest of the reference live in docs/SDK.md.

Module formats and subpath entries

The package ships ESM and CommonJS (require works, for older Next.js setups and CJS toolchains), plus a subpath entry per component:

import { init, mountForm } from "@leadbot/components/form";

form, chat, whatsapp, buttons and flow each carry the shared configuration surface (init, loadFlow, useStep) and nothing from the other components — a form-only page is ~24 kB gzipped instead of ~81 kB. The barrel import is tree-shakeable too; the subpath is the guarantee.

Configuration precedence

The value on the mount always wins. Everything in init() is a default; an option set on a mount*() call overrides it for that mount only, and an option left undefined overrides nothing. leadbotId included — set it once in init(), and pass it per mount only where an embed belongs to a different component leadbot.

Published steps

Every component is backed by a published step. Its id is the step uuid, and it configures the component from the dashboard — form fields, labels, WhatsApp number, agent:

mountForm(document.querySelector("#demo")!, { leadbotId: 18, id: "step-uuid" });

The flow is fetched once per leadbot per page and shared by every component; that request is also what counts the pageview. Options you pass explicitly still win over the published step, but the id itself is not optional: it is also what the collector validates submissions and chat turns against.

Because the published flow is the single source of truth, unpublishing a bot removes its components from every page — they render nothing until it is published again.

Theming

Everything is restylable, at whichever level suits you:

  1. Tokens — override a custom property on .leadbot-components or any ancestor (including :root). Defaults are declared on :root inside @layer leadbot, so an inherited value always wins.
  2. Your own rules — every default in @leadbot/components/styles.css lives in @layer leadbot, so any unlayered rule of yours beats it regardless of specificity. No !important needed: .leadbot-components .leadbot-chatbot-message-bot { background: #111; }
  3. Start from scratch — don't import styles.css at all. The markup is plain light DOM with the stable class names listed under Class names; style it however you like.
/* 1. tokens */
.my-section {
  --leadbot-primary: #059669;
  --leadbot-font-family: "Söhne", system-ui, sans-serif;
  --leadbot-radius-card: 4px;
}

Dark mode

Set data-leadbot-theme="dark" on <html>, <body> or any ancestor section — every mount inside it switches to the built-in dark token set (surfaces, text, borders, feedback colors and the WhatsApp backdrop; layout and the brand greens are unchanged). data-leadbot-theme="auto" follows the OS prefers-color-scheme instead. It's all tokens inside @layer leadbot, so your own overrides keep winning either way.

<html data-leadbot-theme="dark">   <!-- or "auto" -->

Tokens

| Property | Default | Purpose | |---|---|---| | --leadbot-primary | #2563eb | Buttons, accents, user chat bubbles, focus rings | | --leadbot-available-height | 600px | Max height of chat / form panels | | --leadbot-chat-height | 520px | Fixed height of the chat panel — it never grows or shrinks with the conversation. Capped by the available height | | --leadbot-form-max-width | 360px | Form card width cap; set none for full width | | --leadbot-form-max-height | available height | Form card height cap; set none to never scroll | | --leadbot-font-family | page default | Type stack; undeclared so your page's font shows through | | --leadbot-font-size | 14px | Body text | | --leadbot-font-size-sm / -xs / -lg | 13px / 12px / 18px | Secondary text, captions, form titles | | --leadbot-color-text | #1f2937 | Body text | | --leadbot-color-text-strong | #374151 | Menu items, table headers | | --leadbot-color-text-body | #4b5563 | Compact labels, quoted text | | --leadbot-color-text-muted | #6b7280 | Timestamps, citations | | --leadbot-color-text-subtle | #9ca3af | Placeholders, typing dots, branding | | --leadbot-color-on-primary | #ffffff | Text/icons on primary and brand fills | | --leadbot-color-surface | #ffffff | Panels, inputs, menus | | --leadbot-color-surface-alt | #f9fafb | Table headers, menu hover | | --leadbot-color-surface-sunken | #f3f4f6 | Bot chat bubbles | | --leadbot-color-surface-tray / -tray-alt | #f8f9fa / #f0f0f0 | Composer trays | | --leadbot-color-border | #e5e7eb | Dividers, default borders | | --leadbot-color-border-input / -field / -checkbox | #ddd / #e0e3e8 / #d1d5db | Field borders | | --leadbot-color-disabled | #ccc | Disabled send button | | --leadbot-color-logo | #111827 | "Powered by" wordmark | | --leadbot-color-error | #ef4444 | Field validation | | --leadbot-color-error-bg / -fg / -border | #fee / #c33 / #fcc | Form error banner | | --leadbot-color-success | #059669 | Success message | | --leadbot-color-success-text | #065f46 | "Opened in WhatsApp" confirmation | | --leadbot-color-online | #34d399 | Agent online dot | | --leadbot-focus-ring / -soft | primary @ 15% / 12% | Focus rings on text controls | | --leadbot-radius-card | 12px | Panel border radius | | --leadbot-radius-sm / -md / -pill | 6px / 8px / 20px | Inputs / buttons / pills | | --leadbot-spacing-card | 16px | Panel padding | | --leadbot-gradient-card | white gradient | Form card background | | --leadbot-border-light | 1px solid rgba(0,0,0,.12) | Card border | | --leadbot-shadow-sm / -md / -lg | — | Elevation | | --leadbot-duration-fast / -normal / -slow | 150ms / 300ms / 500ms | Transitions | | --leadbot-ease-appear / -interactive / -loop / -bounce | — | Easing curves | | --leadbot-whatsapp-green / -hover / -on-green | #42d66b / #35c95d / #111b21 | WhatsApp panel chrome | | --leadbot-whatsapp-wallpaper / -end | #e5ddd5 / #d9d0c4 | WhatsApp chat backdrop | | --leadbot-whatsapp-bubble / -text | #ffffff / #303030 | WhatsApp message bubble | | --leadbot-whatsapp-tick | #4fc3f7 | Read receipts | | --leadbot-whatsapp-brand / -hover / -dark | #25d366 / #1fb959 / #128c7e | Official WhatsApp brand fills |

Alternatively pass baseColor (e.g. baseColor: "#ff0076" in a mount, the React/Vue provider, or init()) — it sets --leadbot-primary on the mount container for you.

The spacing scale, circular radii, fixed element sizes (avatars, icons, send buttons) and one-off shadows are deliberately not tokenized — override them with a rule against the class instead (level 2 above).

Class names

The components render the widget's own step markup, so these are the classes the widget uses too. Stable and semantic; tests/components_styles.test.ts fails if a component renders a class the stylesheet doesn't style.

  • Forms — leadbot-form · leadbot-callback-form · leadbot-email-request-form · leadbot-form-shell · leadbot-form-scroll · leadbot-form-title · leadbot-form-fields · leadbot-form-actions · leadbot-form-error · leadbot-form-complete · leadbot-success-message · leadbot-submit-btn (+ leadbot-loading) · leadbot-field · leadbot-field-checkbox · leadbot-field-error · leadbot-field-list · leadbot-field-list-label · leadbot-field-options · leadbot-field-option · leadbot-required-asterisk, and the date and time pickers' leadbot-datepicker-* / leadbot-timepicker-*
  • Agent chat — leadbot-chatbot-interface and its -header, -header-info, -title, -status, -online-dot, -menu-btn, -menu, -menu-item, -menu-item-icon, -messages, -message, -message-bot, -message-user, -message-typing, -message-content, -thinking, -thinking-text, -typing-dots, -citations, -citation-item, -citation-number, -citation-url, -starters, -starter-btn, -input, -input-field, -message-input, -send-btn (+ --active) suffixes
  • WhatsApp — leadbot-whatsapp-interface and its -header, -contact-info, -contact-details, -name, -status, -chat, -message, -message-content, -input-container, -input-bar, -message-field, -send-btn suffixes, plus leadbot-whatsapp-send-disabled
  • Buttons — leadbot-options (also leadbot-buttons) holding leadbot-option-btn (also leadbot-buttons-btn; -secondary without a step colour, -icon for tel:/mailto:)

DOM anatomy

The class names alone don't tell you which element carries what, so here is the tree each component renders. This nesting is public API — it changes only on a major version, and display: contents on a wrapper marked (structural) is a supported way to flatten it.

div.leadbot-components               mount container (yours; also .leadbot-bare)
└─ div.leadbot-form                surface: background, border, shadow, radius,
   │                               size caps (also .leadbot-callback-form /
   │                               .leadbot-email-request-form)
   └─ form.leadbot-form-shell      (structural) flex column
      ├─ div.leadbot-form-scroll   (structural) card padding + scroll region
      │  ├─ div.leadbot-form-title
      │  ├─ div.leadbot-form-error
      │  └─ fieldset.leadbot-form-fields   (structural)
      │     └─ div.leadbot-field[data-field][data-field-type] × N
      │        ├─ label
      │        ├─ input | select | textarea   ([aria-invalid] on error)
      │        └─ p.leadbot-field-error        the field's message
      └─ div.leadbot-form-actions  surface: own background + top border
         └─ button.leadbot-submit-btn

After a successful submit the whole tree is replaced by:

div.leadbot-form.leadbot-form-complete
└─ div.leadbot-success-message

Every field carries data-field="<name>" and data-field-type="<canonical type>" — target those instead of :nth-child, which silently re-pairs when fields are reordered in the dashboard.

Chrome, size and layout

Three opinions in the default styling are widget-isms, and each has an off switch:

  • chrome: false on any mount removes the card surface, the size caps, the scroll region and the actions bar, leaving only functional styling (field stacking, focus rings, error and success states). Use it whenever the form is part of your page rather than a floating panel.
  • --leadbot-form-max-width (360px) and --leadbot-form-max-height (--leadbot-available-height) — set either to none to keep the card but drop the cap.
  • layout: "inline" on mountForm puts the submit button on the same row as the last field; earlier fields span the full width. Pairing is positional.

Recipes

/* Full-width form, no card — pairs with chrome: false */
.leadbot-components .leadbot-field input { border-radius: 999px; padding: 14px 18px; }
.leadbot-components .leadbot-submit-btn  { border-radius: 999px; width: auto; padding: 14px 28px; }
/* Inline "url + email + button" capture row, 2/3 – 1/3 */
mountForm(el, { leadbotId: 18, id: stepUuid, chrome: false, layout: "inline", hideLabels: true });
/* ...and the 2/3 – 1/3 split, by field name rather than by position */
.leadbot-components .leadbot-form-shell { grid-template-columns: 2fr 1fr; }
.leadbot-components [data-field="email"] { grid-column: 1; }
<!-- Dark section — full built-in dark theme for the mounts inside it -->
<section data-leadbot-theme="dark">...</section>

Components ships no focus-visible, reduced-motion or responsive rules (those are widget-only partials) — if you replace styles.css wholesale, those states are yours to provide.

Mounts

  • mountAgentChat(el, { leadbotId, id, initialMessage?, conversationStarters?, placeholder?, labels?, eventLabel?, chatSessionId?, onMessage?, onError?, className? })
  • mountForm(el, { leadbotId, id, fields?, stepType?, submitLabel?, successMessage?, eventLabel?, marksAsQualified?, onSubmit?, onSuccess?, onError?, labels?, className?, chrome?, layout?, hideLabels? }) — chrome: false drops the widget card, layout: "inline" puts the button beside the last field, hideLabels: true keeps labels for screen readers only and uses them as placeholders
  • mountCallbackRequest(el, options) — mountForm preset (name + phone, stepType: "callback_request"); the published step or an explicit fields array overrides the preset
  • mountEmailRequest(el, options) — preset (name + email + message, stepType: "email_request")
  • mountWhatsApp(el, { leadbotId, id, phoneNumber?, prefilledMessage?, ... }) — records the interaction, then opens WhatsApp
  • mountButtons(el, { leadbotId, id, buttons?, eventLabel?, onSelect?, className? }) — renders a published step's options as buttons; an option with a next_step_uuid opens the page widget at that step and sends its label as the first message; an explicit buttons array overrides the published options. A button may set children (any markup — icons etc.) to render instead of its label; the label still identifies the button for tracking and onSelect
  • mountFlow(el, { leadbotId?, typingIndicator?, ... }) — renders a full leadbot flow (a standard bot marked "served via SDK") inline in your page, with no widget chrome
  • openWidget(params?: { stepUuid?, message? }) / closeWidget() / toggleWidget() — control an embedded LeadBot widget on the same page; passing both stepUuid and message navigates to that step and sends the message
  • clearFlowCache() — drops the page-lifetime boot-fetch cache, so the next mount (or an SPA route change) refetches the published flow

Full documentation: docs/SDK.md in the repo (published docs mirror it).

Embedded flows

A standard leadbot flow — built in the dashboard and marked Settings → Serve via SDK — renders inline anywhere on your page via mountFlow(). It's the full conversation flow (all steps, routing, state) with no widget chrome: no floating panel, toggle button, or fixed positioning. Your host element decides the size and placement.

mountFlow(document.querySelector("#leadbot-flow")!, {
  leadbotId: 18,  // falls back to init() default
  typingIndicator: true,  // show typing dots and navigation delay (default)
});

The bot ID can be passed explicitly or defaults to the leadbotId in init(). The boot fetch selects the bot by ID, counts the pageview, and respects the bot's visible URLs setting — if the page falls outside that scope, nothing renders.

Import @leadbot/components/styles.css for the default widget-like look (scoped to .leadbot-flow), or style the .leadbot-* markup yourself. The publication's dashboard base color applies automatically; pass baseColor to override it.

Conversation state restores across page navigations if the bot has keep open on navigation enabled (stored per leadbot in sessionStorage). On SPA route changes, a new pageview is billed and the conversation restarts unless the restore setting keeps it open.

Multiple mountFlow() calls with different leadbotIds can coexist on one page, each maintaining separate conversation state. A call returns { update(options), destroy() } — update() re-renders in place and preserves conversation history, while destroy() cleans up. The classic LeadBot widget (via window.LeadBot or script.js) remains unaffected.

Same renderer as the widget

Forms, chat, WhatsApp and buttons now render the widget's own step components and styles, so a published step looks and behaves the same in both. What that changes for an existing integration:

  • Validation — empty required fields and malformed emails get the browser's own message, as in the widget; labels.required, invalidEmail and invalidNumber are deprecated. Phone numbers are cleaned up before they are checked and sent (0031 6 … → +316…), and server errors land on their field in the bot's language.
  • Data — checklists reach the collector and onSubmit/onSuccess as arrays (they were a JSON string); checkboxes stay booleans. Field types the widget doesn't know are skipped instead of shown as a text box. Dates and times use the widget's pickers.
  • Chat — shows the step's sender name and avatar, a "powered by" line (hidden for white-label bots), feedback buttons, and the forms and WhatsApp hand-offs the agent opens mid-conversation. The conversation survives a page load within the tab.
  • WhatsApp — the QR code on desktop, the phone-number question and the page link in the message all follow the step's settings. onSuccess fires once the message is saved.
  • Buttons — option conditions (device, day, time) apply; an option that leads to a button step shows that button, so tel:, mailto: and links open directly; each click is tracked against the button's own step.
  • Flow — the dashboard preview's submissions are flagged as preview, and new steps scroll into view.

Upgrading to 0.5

0.5 makes the published step the source of truth, which is a breaking change for every component:

  • id is now required on every mount and component. It is the uuid of the published step, from the dashboard. Without it a component renders nothing.
  • agentId is gone from mountAgentChat — pass the chat step's id instead and the collector resolves the agent from it.
  • phoneNumber is now optional on mountWhatsApp; omit it to use the published step's number.
  • init({ leadbotId }) is new. Without it there is no boot fetch, so no pageview is counted and no GA events fire on a first-ever visit. Pass it to get the same tracking as the classic widget.
- mountAgentChat(el, { leadbotId: 18, agentId: "agent-uuid" });
+ mountAgentChat(el, { leadbotId: 18, id: "chat-step-uuid" });