← Home

#React Widget

Drop-in chat widget and hooks for React 18/19. Built on @bootdesk/js-web-adapter-core.

#Installation

npm install @bootdesk/js-web-adapter-react @bootdesk/js-web-adapter-core

Peer deps: react ^18 || ^19, react-dom, marked ^18, dompurify ^3.4.

Import the CSS:

import "@bootdesk/js-web-adapter-react/styles.css";

#ChatWidget

The main component. Three display modes: floating, fullscreen, and embedded.

#Floating Mode (default)

import { ChatWidget } from "@bootdesk/js-web-adapter-react";
import { WebChatClient } from "@bootdesk/js-web-adapter-core";

const client = new WebChatClient({
  apiUrl: "https://your-app.com",
  userId: "user-123",
  userName: "Alice",
});

function App() {
  return (
    <ChatWidget
      client={client}
      title="Support"
      placeholder="Type your question..."
      position="bottom-right"
      theme="auto"
    />
  );
}

#Fullscreen Mode

<ChatWidget client={client} initialMode="fullscreen" />

#Embedded Mode

Takes the full size of its parent container. Use when you want to render the chat inline on a page or inside an iframe.

<div style={{ height: "600px" }}>
  <ChatWidget client={client} embedded />
</div>

Auto-detects iframe — when inside an <iframe>, switches to embedded mode automatically and listens for chat-config messages from the parent page via useIframeBridge.

#Props

Prop Type Default
client WebChatClient required
initialMode "floating" | "fullscreen" "floating"
theme "light" | "dark" | "auto" "auto"
title string "Chat"
placeholder string "Type a message..."
position "bottom-right" | "bottom-left" | "top-right" | "top-left" "bottom-right"
embedded boolean false
showClose boolean true
showFullscreenToggle boolean true
enableAttachments boolean false
uploadConfig UploadConfig?
accept string?
maxFileSize number?
onOpen () => void?
onClose () => void?
onThemeChange (theme) => void?
floatingButton.icon ReactNode? chat bubble SVG
floatingButton.badgeCount number?
floatingButton.size number 56
floatingButton.backgroundColor string? var(--chat-primary)
className { container?, header?, messageList?, inputArea? }?
preEntry { render: (helpers: { start: (config?) => void }) => ReactNode }?
onChatStart (config?: ReconfigureConfig) => void?

#Pre-Entry Screen

Show a custom form before the conversation starts — useful for collecting a name, email, verification code, or terms acceptance. The developer controls all logic; call start(config) when ready.

The config passed to start() is forwarded to client.reconfigure() (see JS Core → Reconfiguration), updating the client's identity before messages load.

function EmailVerificationForm({ start }) {
  const [email, setEmail] = useState("");
  const [code, setCode] = useState("");
  const [step, setStep] = useState("email");
  const [error, setError] = useState("");

  const handleEmailSubmit = async (e) => {
    e.preventDefault();
    // Send code to user's email
    const { id } = await fetch("/api/request-code", {
      method: "POST",
      body: JSON.stringify({ email }),
    }).then((r) => r.json());
    setStep("code");
  };

  const handleCodeSubmit = async (e) => {
    e.preventDefault();
    const data = await fetch("/api/verify-code", {
      method: "POST",
      body: JSON.stringify({ code }),
    }).then((r) => r.json());
    // Configure the client and start the conversation
    start({ userId: data.userId, verifyToken: data.verifyToken });
  };

  if (step === "email") {
    return (
      <form onSubmit={handleEmailSubmit}>
        <h2>Welcome!</h2>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="your@email.com"
          required
        />
        <button type="submit">Send Code</button>
        {error && <p style={{ color: "red" }}>{error}</p>}
      </form>
    );
  }

  return (
    <form onSubmit={handleCodeSubmit}>
      <h2>Enter the 6-digit code</h2>
      <input
        type="text"
        value={code}
        onChange={(e) => setCode(e.target.value)}
        placeholder="000000"
        maxLength={6}
        required
      />
      <button type="submit">Verify</button>
    </form>
  );
}

function App() {
  return (
    <ChatWidget
      client={client}
      title="Support"
      preEntry={{
        render: ({ start }) => <EmailVerificationForm start={start} />,
      }}
      onChatStart={(config) => {
        // Persist session so returning users skip the form
        document.cookie = `session=${JSON.stringify(config)}; max-age=604800`;
      }}
    />
  );
}

Messages only begin loading after start() is called. While the pre-entry form is shown, the header remains visible (close button, theme toggle, etc.). Works in all three display modes (floating, fullscreen, embedded).

#Hooks

#useChatClient

Creates a WebChatClient, calls connect() on mount and disconnect() on unmount.

import { useChatClient } from "@bootdesk/js-web-adapter-react";

function MyComponent() {
  const client = useChatClient({
    apiUrl: "https://your-app.com",
    userId: "user-123",
    userName: "Alice",
  });

  return <div>{/* ... */}</div>;
}

#useMessages

Full message state management.

import { useChatClient, useMessages } from "@bootdesk/js-web-adapter-react";

function Chat() {
  const client = useChatClient({ apiUrl, userId, userName });
  const {
    messages,
    loading,
    hasMore,
    loadMore,
    sendMessage,
    editMessage,
    deleteMessage,
    addReaction,
    removeReaction,
    canEdit,
    canDelete,
    canReact,
  } = useMessages(client);

  return (
    <div>
      <button onClick={() => sendMessage("Hi!")} disabled={loading}>
        Send
      </button>
      <button onClick={() => addReaction("msg-1", "👍")}>
        Like
      </button>
      {messages.map((msg) => (
        <div key={msg.id}>{msg.content.text}</div>
      ))}
      {hasMore && <button onClick={loadMore}>Load more</button>}
    </div>
  );
}

#useStreaming

import { useStreaming } from "@bootdesk/js-web-adapter-react";

function StreamView({ client }) {
  const { streamingMessages, isStreaming } = useStreaming(client);
  return <div>{/* streamingMessages: Map<string, string> */}</div>;
}

#useTyping

import { useTyping } from "@bootdesk/js-web-adapter-react";

function Typing({ client }) {
  const { isSomeoneTyping } = useTyping(client);
  return isSomeoneTyping ? <span>Typing...</span> : null;
}

#useAttachmentUpload

const { pendingAttachments, upload, removePending, progress } =
  useAttachmentUpload(uploadConfig);

#Cards

Cards are rich interactive elements sent from your backend. The widget renders them automatically.

#Custom Card Renderers

Register custom renderers via CardProvider:

import { CardProvider, CardRenderer, DefaultCard } from "@bootdesk/js-web-adapter-react";

function App() {
  return (
    <CardProvider
      renderers={{
        "my-custom-card": MyCustomCard,
        "default": DefaultCard,
      }}
    >
      <ChatWidget client={client} />
    </CardProvider>
  );
}

function MyCustomCard({ card }) {
  return <div className="my-card">{card.data.text}</div>;
}

ChatProvider wraps CardProvider automatically if you prefer a single provider:

import { ChatProvider, ChatWidget } from "@bootdesk/js-web-adapter-react";

<ChatProvider>
  <ChatWidget client={client} />
</ChatProvider>;

#Internationalisation

Seven built-in locales: en, en-US, en-GB, pt, pt-BR, pt-PT, es.

import { LocaleProvider, useLocale, registerLocale } from "@bootdesk/js-web-adapter-react";

// Override a locale at runtime
registerLocale("pt-BR", {
  "chat.header.title": "Atendimento",
  "chat.input.placeholder": "Digite sua mensagem...",
});

function App() {
  return (
    <LocaleProvider locale="pt-BR">
      <ChatWidget client={client} />
    </LocaleProvider>
  );
}

#Theming

#CSS Variables

Override on your root element:

:root {
  --chat-primary: #4f46e5;
  --chat-background: #ffffff;
  --chat-text: #0f172a;
  --chat-border: #e2e8f0;
  --chat-surface: #f8fafc;
}

Dark mode values are set automatically when theme="auto" or theme="dark".

#Theme Toggle

The widget's header includes a theme toggle button by default. Listen for changes with onThemeChange. Theme is persisted to localStorage key chat-theme.

#Responsive

Screens narrower than 800px automatically switch to fullscreen. Floating position and drawer are optimised for mobile — dvh units keep the input visible on iOS, and interactive-widget=resizes-content viewport meta is managed for Android.

#Sub-components

You can also use individual components for a custom layout:

import { Header, MessageList, InputArea, TypingIndicator } from "@bootdesk/js-web-adapter-react";

function CustomChat({ client }) {
  const { messages, sendMessage } = useMessages(client);
  const { isSomeoneTyping } = useTyping(client);

  return (
    <div className="my-chat">
      <Header title="Support" />
      <MessageList messages={messages} />
      {isSomeoneTyping && <TypingIndicator />}
      <InputArea onSend={sendMessage} />
    </div>
  );
}