Package Exports
- react-agent-widget
Readme
react-agent-widget
→ Live demo on CodeSandbox — no install, no API key, works in your browser.
The fastest way to add a production-ready AI chatbot to any React app.
Drop in one component. Connect to OpenAI, Anthropic Claude, AWS Bedrock, Azure OpenAI, or your own API. Get a streaming chat UI with a floating launcher button, full theming, and zero style conflicts — in under 10 lines of code.
Why react-agent-widget?
| Feature | react-agent-widget |
|---|---|
| Works with any AI provider | OpenAI, Anthropic, AWS Bedrock, Azure OpenAI, custom HTTP |
| Streaming responses | Built-in SSE parser, token-by-token rendering |
| Fully themeable | CSS custom properties — change every color, radius, shadow, font |
| Generative UI | Agent renders React components (cards, forms, charts) inside the chat |
| TypeScript-first | Full type safety, no any in the public API |
| Zero CSS conflicts | Scoped raw- class prefix, no global styles, no CSS-in-JS runtime |
| Headless mode | Use your own UI with the built-in state/streaming hooks |
| Lightweight | ~44 KB minified ESM, React 18 peer dep, no mandatory runtime deps |
| SSR-safe | No window access at render time — works with Next.js App Router |
Installation
npm install react-agent-widget
# or
yarn add react-agent-widget
# or
pnpm add react-agent-widgetRequires React 18+ as a peer dependency.
Quick start — 10 lines
import { AgentWidget, createHttpAdapter } from 'react-agent-widget'
const adapter = createHttpAdapter({ url: '/api/chat' })
export default function App() {
return (
<AgentWidget
adapter={adapter}
welcomeMessage="Hi! How can I help you today?"
/>
)
}Your /api/chat endpoint receives { messages, stream } and returns a streaming SSE response or plain JSON. No API keys go in the browser — your server adds credentials.
That's it. A floating chat button appears at the bottom-right of the page.
Connecting to AI providers
All adapters point at a proxy endpoint you control on your server. Your server adds the API key, SigV4 signature, or managed identity — never the browser.
OpenAI / ChatGPT
import { AgentWidget, createOpenAIAdapter } from 'react-agent-widget'
<AgentWidget
adapter={createOpenAIAdapter({
proxyUrl: '/api/openai', // your server forwards to api.openai.com + adds Authorization header
model: 'gpt-4o',
})}
/>Anthropic Claude
import { AgentWidget, createAnthropicAdapter } from 'react-agent-widget'
<AgentWidget
adapter={createAnthropicAdapter({
proxyUrl: '/api/anthropic', // your server adds x-api-key + Anthropic-Version headers
model: 'claude-3-5-sonnet-20241022',
})}
/>AWS Bedrock
import { AgentWidget, createBedrockAdapter } from 'react-agent-widget'
<AgentWidget
adapter={createBedrockAdapter({
proxyUrl: '/api/bedrock', // your server adds SigV4 auth
model: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
})}
/>Azure OpenAI
import { AgentWidget, createAzureOpenAIAdapter } from 'react-agent-widget'
<AgentWidget
adapter={createAzureOpenAIAdapter({
proxyUrl: '/api/azure', // your server adds api-key / managed identity
deploymentName: 'gpt-4o',
})}
/>Custom HTTP endpoint / BFF
import { AgentWidget, createHttpAdapter } from 'react-agent-widget'
<AgentWidget
adapter={createHttpAdapter({
url: '/api/chat',
// optional: customize the request body
formatRequest: (messages, options) => ({
history: messages,
config: { stream: true },
}),
})}
/>Full API
<AgentWidget
// required
adapter={adapter}
// launcher button
launcher={{
position: { side: 'right' }, // 'left' | 'right'
label: 'Chat with AI',
icon: <MyIcon />, // custom icon
badge: 3, // unread count bubble
}}
// theme — override any token
theme={{
mode: 'dark', // 'light' | 'dark'
colors: {
primary: '#7c3aed',
userBubble: '#7c3aed',
background: '#09090b',
},
radius: { panel: '20px', launcher: '12px' },
}}
// per-slot class names (add your own CSS on top)
classNames={{
panel: 'my-panel',
messageUser: 'my-user-msg',
messageAgent: 'my-agent-msg',
composer: 'my-composer',
}}
// chat behavior
welcomeMessage="Hi! How can I help?"
suggestedPrompts={['Summarize this page', 'Help me write an email']}
systemPrompt="You are a helpful assistant for Acme Corp."
placeholder="Ask me anything…"
renderMarkdown={true} // render markdown in agent replies (default: true)
// generative UI — agent renders React components
components={{
weatherCard: WeatherCard,
approvalPrompt: ApprovalPrompt,
}}
// events
onMessage={(msg) => analytics.track('chat_message', msg)}
onError={(err) => errorReporter.capture(err)}
onOpen={() => analytics.track('chat_opened')}
onClose={() => analytics.track('chat_closed')}
// controlled open state (optional)
open={isOpen}
onOpenChange={setIsOpen}
/>Theming
The widget uses CSS custom properties for all visual tokens. Pass a theme object — the widget applies the values as inline CSS variables on its root element, so your host-app styles are never affected.
// Dark purple theme example
<AgentWidget
adapter={adapter}
theme={{
mode: 'dark',
colors: {
primary: '#8b5cf6',
primaryForeground: '#ffffff',
background: '#09090b',
surface: '#18181b',
userBubble: '#8b5cf6',
agentBubble: '#27272a',
agentBubbleForeground: '#fafafa',
},
radius: {
panel: '24px',
message: '20px',
launcher: '16px',
},
}}
/>Available CSS variables (for stylesheet overrides)
/* Colors */
--raw-color-primary --raw-color-primary-fg
--raw-color-background --raw-color-surface --raw-color-surface-fg
--raw-color-border --raw-color-muted --raw-color-muted-fg
--raw-color-error --raw-color-error-fg
--raw-color-user-bubble --raw-color-user-bubble-fg
--raw-color-agent-bubble --raw-color-agent-bubble-fg
/* Typography */
--raw-font-family --raw-font-size-sm --raw-font-size-md --raw-font-size-lg
/* Radius */
--raw-radius-panel --raw-radius-message
--raw-radius-launcher --raw-radius-composer
/* Shadow / z-index */
--raw-shadow-panel --raw-shadow-launcher
--raw-z-panel --raw-z-launcherGenerative UI
Let the AI render interactive React components directly inside the chat — not just text.
// 1. Define your component
function WeatherCard({ payload, onResult }) {
const { city, temperature, condition } = payload.props
return (
<div style={{ padding: 16, borderRadius: 12, background: '#f0f9ff' }}>
<h3>{city} — {temperature}°C, {condition}</h3>
<button onClick={() => onResult?.({ action: 'show_forecast', city })}>
Show 7-day forecast
</button>
</div>
)
}
// 2. Register it
<AgentWidget
adapter={adapter}
components={{ weatherCard: WeatherCard }}
/>// 3. Your agent/tool emits this SSE event:
data: {"type":"component","payload":{"type":"weatherCard","props":{"city":"London","temperature":15,"condition":"Cloudy"}}}The widget renders <WeatherCard> in-stream. When the user clicks the button, onResult fires and the widget sends the result back into the conversation automatically.
Headless mode
Bring your own UI — use just the state management and streaming logic.
import {
WidgetProvider,
useWidget,
ThemeProvider,
createOpenAIAdapter,
} from 'react-agent-widget'
const adapter = createOpenAIAdapter({ proxyUrl: '/api/openai' })
function MyChatUI() {
const { messages, sendMessage, isStreaming, isOpen, setIsOpen } = useWidget()
return (
<div>
{messages.map(msg => (
<div key={msg.id} className={msg.role}>
{msg.content}
</div>
))}
<input
onKeyDown={(e) => {
if (e.key === 'Enter') void sendMessage(e.currentTarget.value)
}}
/>
</div>
)
}
export function App() {
return (
<ThemeProvider>
<WidgetProvider adapter={adapter}>
<MyChatUI />
</WidgetProvider>
</ThemeProvider>
)
}Writing a proxy endpoint
Your backend receives the messages and forwards them to the AI provider. Here is a minimal Next.js example for OpenAI:
// app/api/openai/route.ts
import OpenAI from 'openai'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
export async function POST(req: Request) {
const { messages } = await req.json()
const stream = openai.beta.chat.completions.stream({
model: 'gpt-4o',
messages,
})
return new Response(stream.toReadableStream(), {
headers: { 'Content-Type': 'text/event-stream' },
})
}The widget's SSE parser understands OpenAI's native streaming format automatically.
SSE format (custom backends)
If you write your own backend, use this simple format:
data: {"type":"text-delta","delta":"Hello"}
data: {"type":"text-delta","delta":", world!"}
data: {"type":"component","payload":{"type":"weatherCard","props":{...}}}
data: {"type":"done"}Or just return data: [DONE] as the final event (OpenAI convention — also understood).
Building a custom Adapter
Implement the Adapter interface to connect any AI service:
import type { Adapter } from 'react-agent-widget'
export const myAdapter: Adapter = {
name: 'my-provider',
capabilities: { streaming: true, tools: false, multimodal: false },
async *send(messages, options) {
const res = await fetch('/api/my-llm', {
method: 'POST',
body: JSON.stringify({ messages }),
signal: options?.signal,
})
// yield chunks…
yield { type: 'text-delta', delta: 'Hello' }
yield { type: 'done' }
},
}TypeScript
Full type safety — no any in the public API.
import type {
Adapter,
AdapterCapabilities,
StreamChunk,
Message,
Theme,
ClassNamesConfig,
AgentWidgetProps,
GenerativeUIComponent,
} from 'react-agent-widget'Accessibility
- Keyboard navigable — Tab, Enter, Escape to close, full focus management
- ARIA:
role="dialog",aria-modal,aria-live="polite"on message list - Respects
prefers-reduced-motion(animations disabled automatically) - Default theme meets WCAG 2.1 AA contrast ratios
Browser & framework support
| Environment | Supported |
|---|---|
| React | 18+ |
| Next.js (App Router) | Yes — SSR-safe |
| Next.js (Pages Router) | Yes |
| Vite | Yes |
| Remix | Yes |
| Webpack | Yes |
| Browsers | All modern evergreen (Chrome, Firefox, Safari, Edge) |
License
MIT — TilangaPramith