React components SDK Planned
The React package wraps the message.com widget into idiomatic components and a hook. Use <MessageWidget> for the drop-in launcher, or useMessage for programmatic control. SSR-safe, Next.js compatible, fully typed.
Q2 2026 on the roadmap. Until then you can use the raw <script> snippet inside your app/layout.tsx or via the Next.js Script component without losing anything material.
Installation
The package is React 18 and 19 compatible and ships ESM only. Tree-shaking removes the parts of the surface you do not import.
npm install @message-com/react
# or
pnpm add @message-com/react
# or
yarn add @message-com/reactThe MessageWidget component
The simplest case: drop the component near the root of your app. It mounts the launcher, manages its own lifecycle, and cleans up on unmount.
import { MessageWidget } from "@message-com/react";
export default function App() {
return (
<>
<YourApp />
<MessageWidget workspaceId="abc123" />
</>
);
}Props
workspaceId(required). Your workspace ID, also called the public site identifier.color. Override the workspace primary colour.position."right"(default) or"left".bottomSpacing. Pixels from the bottom edge.defaultOpen. Iftrue, opens the panel on mount.noLauncher. Renders without the launcher; pair withuseMessage.
The useMessage hook
For fine-grained control, use the hook. It exposes a typed API for opening, closing, identifying, sending, and subscribing to messages.
import { useMessage } from "@message-com/react";
function UpgradePrompt() {
const { open, identify, isReady } = useMessage();
function startSalesChat(user) {
identify({ email: user.email, userId: user.id });
open();
}
return (
<button disabled={!isReady} onClick={() => startSalesChat(currentUser)}>
Talk to sales
</button>
);
}Return shape
open(),close(),toggle().identify(visitor),update(attrs),signout().send(body),messages,status.isReady.trueonce the bundle has finished loading.on(event, handler). Subscribe to widget events.
Next.js
The component is SSR-safe; it renders nothing on the server and hydrates the launcher on the client. Drop it in the root layout.
// app/layout.tsx, Next.js App Router
import { MessageWidget } from "@message-com/react";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
<MessageWidget workspaceId={process.env.NEXT_PUBLIC_MESSAGE_WORKSPACE!} />
</body>
</html>
);
}Headless chat surface
If you want to render your own UI (in-product help drawer, full-page support centre, embedded chat in a sidebar), set noLauncher and drive the conversation through the hook. The hook exposes the raw message stream.
import { useMessage } from "@message-com/react";
function HeadlessChat() {
const { messages, send, status } = useMessage();
return (
<div>
{messages.map((m) => (
<div key={m.id} className={m.kind}>{m.body}</div>
))}
<form onSubmit={(e) => { e.preventDefault(); send(input); }}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button disabled={status !== "connected"}>Send</button>
</form>
</div>
);
}React StrictMode
The component is StrictMode-safe; the underlying widget bundle is initialised exactly once across the double-render. No useRef tricks required.
Common pitfalls
- Do not render
<MessageWidget>per-route. Put it once at the layout level; otherwise it remounts on every navigation. - Do not pass dynamic
workspaceIdafter first mount. Changing it forces a full reinitialisation. - SSR identify must move to
useEffect. Callingidentifyat module scope will throw on the server.