<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tilanga Pramith</title>
    <description>The latest articles on DEV Community by Tilanga Pramith (@pramith_vidusara_6c6880ec).</description>
    <link>https://dev.to/pramith_vidusara_6c6880ec</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4010704%2F7e0e0d02-f922-4c9c-847c-9282b10c08b0.jpg</url>
      <title>DEV Community: Tilanga Pramith</title>
      <link>https://dev.to/pramith_vidusara_6c6880ec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pramith_vidusara_6c6880ec"/>
    <language>en</language>
    <item>
      <title>I built a drop-in AI chatbot widget for React that works with any provider — here's why</title>
      <dc:creator>Tilanga Pramith</dc:creator>
      <pubDate>Wed, 01 Jul 2026 09:56:12 +0000</pubDate>
      <link>https://dev.to/pramith_vidusara_6c6880ec/i-built-a-drop-in-ai-chatbot-widget-for-react-that-works-with-any-provider-heres-why-2dl5</link>
      <guid>https://dev.to/pramith_vidusara_6c6880ec/i-built-a-drop-in-ai-chatbot-widget-for-react-that-works-with-any-provider-heres-why-2dl5</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every time I wanted to bolt an AI chatbot onto a React app, I hit the same wall: either I locked myself into one vendor's SDK (OpenAI's widget, Anthropic's whatever-they-ship), or I built the streaming UI, the theming, the floating launcher button, and the SSE parsing &lt;em&gt;again&lt;/em&gt;, from scratch, for the third time this year.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/react-agent-widget" rel="noopener noreferrer"&gt;react-agent-widget&lt;/a&gt;&lt;/strong&gt; — a chat widget that doesn't care which LLM is on the other end.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-agent-widget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick start — 10 lines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AgentWidget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createHttpAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-agent-widget&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHttpAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AgentWidget&lt;/span&gt;
      &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;welcomeMessage&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Hi! How can I help you today?"&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. A floating chat button shows up bottom-right, streaming responses token by token, no extra CSS files, no config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "any provider" matters
&lt;/h2&gt;

&lt;p&gt;Most chatbot widgets I found were tied to one API. The moment you want to switch from OpenAI to Claude, or move to Bedrock because your infra team lives on AWS, you're rewriting the integration layer. &lt;code&gt;react-agent-widget&lt;/code&gt; ships adapters for the providers people actually use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// OpenAI&lt;/span&gt;
&lt;span class="nf"&gt;createOpenAIAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;proxyUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Anthropic Claude&lt;/span&gt;
&lt;span class="nf"&gt;createAnthropicAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;proxyUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/anthropic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-3-5-sonnet-20241022&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// AWS Bedrock&lt;/span&gt;
&lt;span class="nf"&gt;createBedrockAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;proxyUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/bedrock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic.claude-3-5-sonnet-20241022-v2:0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Azure OpenAI&lt;/span&gt;
&lt;span class="nf"&gt;createAzureOpenAIAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;proxyUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/azure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every adapter points at a proxy endpoint &lt;em&gt;you&lt;/em&gt; control — your server attaches the API key, the SigV4 signature, or the managed identity. &lt;strong&gt;No credentials ever ship to the browser.&lt;/strong&gt; That's a deliberate design choice, not an afterthought — I've seen too many "quick" chatbot integrations leak keys in client bundles.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually in the box
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Streaming responses&lt;/td&gt;
&lt;td&gt;Built-in SSE parser, token-by-token rendering — no janky "wait for the whole reply" UX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fully themeable&lt;/td&gt;
&lt;td&gt;CSS custom properties — every color, radius, shadow, and font is overridable, no CSS-in-JS runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generative UI&lt;/td&gt;
&lt;td&gt;The agent can render actual React components (cards, forms, charts) inside the chat, not just text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Headless mode&lt;/td&gt;
&lt;td&gt;Don't like the default UI? Use the state/streaming hooks and build your own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript-first&lt;/td&gt;
&lt;td&gt;Full type safety across the public API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSR-safe&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;window&lt;/code&gt; access at render time — works cleanly with Next.js App Router&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lightweight&lt;/td&gt;
&lt;td&gt;~44KB minified ESM, zero mandatory runtime deps beyond React 18&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Theming without fighting CSS-in-JS
&lt;/h2&gt;

&lt;p&gt;Everything is scoped behind a &lt;code&gt;raw-&lt;/code&gt; class prefix, so it won't collide with your existing styles, and it's themed entirely through CSS variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--raw-primary-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#6366f1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--raw-border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--raw-font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Inter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No runtime style injection, no specificity wars.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it without installing anything
&lt;/h2&gt;

&lt;p&gt;There's a live &lt;a href="https://codesandbox.io/s/dxsrth" rel="noopener noreferrer"&gt;CodeSandbox demo&lt;/a&gt; — no API key needed, just open it and click around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's headed
&lt;/h2&gt;

&lt;p&gt;This is a fresh &lt;code&gt;0.1.x&lt;/code&gt; release, so I'm actively hardening it — better test coverage, more generative UI examples, and I'm looking at a React Native port next since I'm already deep in that ecosystem for another project. If you try it and hit something rough, &lt;a href="https://github.com/TilangaPramith/react-agent-widget/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; — I'm reading all of them.&lt;/p&gt;

&lt;p&gt;If you're building anything that needs an AI chat surface in a React app and don't want to be married to one vendor's SDK, give it a spin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-agent-widget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;📦 &lt;a href="https://www.npmjs.com/package/react-agent-widget" rel="noopener noreferrer"&gt;npm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;a href="https://github.com/TilangaPramith/react-agent-widget" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🧪 &lt;a href="https://codesandbox.io/s/dxsrth" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would genuinely love feedback — especially from anyone who's fighting the same "chatbot per vendor" problem.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
