• Home
  • About
  • Blogs
  • Portfolio
  • Say Hi

Building Framework-Agnostic Web Widgets - A Complete Guide

I had been working on embeddable widgets for a while now, and let me tell you it's way trickier than it looks. You know those little chat bubbles that pop up on websites, or how you can embed a YouTube video anywhere? Yeah, those. They seem simple until you try to build one that doesn't break someone's website.

The Problem Is Real

Here's the thing: your widget needs to work on a WordPress blog from 2015, a brand new React app, and everything in between. It can't mess with their styles, break their JavaScript, or slow down their site. And somehow, it needs to be dead simple to install.

I learned this the hard way when our first widget kept breaking people's sites. Turns out, global CSS is the devil when you're trying to play nice with other people's code.

What I've Learned

Don't use Tailwind or Bootstrap in widgets. I know, I know everyone loves Tailwind. But when your widget lands on a site that's already using it, things get weird fast. Their purge settings, your purge settings, conflicting utilities... it's a mess. Just don't.

Custom elements are your friend. Instead of dumping a <div> somewhere and hoping for the best, use something like <ai-caller-widget>. It's clean, it's semantic, and it doesn't pollute the DOM. The browser knows what to do with it.

React is fine, but keep it simple. I'm using React in my widget, but I'm not pulling in Next.js or anything crazy. Just ReactDOM, create a root, render the component. That's it. The whole bundle stays small, and I don't have to worry about server-side rendering or routing conflicts. Here's what the initialization looks like:

const root = ReactDOM.createRoot(widgetContainer);
root.render(React.createElement(CallWidget));

Nothing fancy. No frameworks, no complex build processes. Just React doing what React does best.

The SPA Problem

This one caught me off guard. Single-page apps don't reload the page when you navigate, so your widget can get stuck on the wrong page or disappear entirely. I had to add this little hack to watch for URL changes:

let lastUrl = location.href;
new MutationObserver(() => {
    const url = location.href;
    if (url !== lastUrl) {
        lastUrl = url;
        setTimeout(initializeWidget, 100);
    }
}).observe(document, { subtree: true, childList: true });

It's not pretty, but it works. The widget reinitializes whenever someone navigates in a React Router app or similar.

Memory Leaks Are Real

I use a WeakMap to track active widget instances:

const activeRoots = new WeakMap();

When a widget gets removed from the DOM, the WeakMap automatically cleans up the reference. No memory leaks, no manual cleanup needed. It's one of those JavaScript features that's actually useful.

Keep It Simple for Users

The embed code is just two lines:

<script src="https://aicaller.com/embed/call-widget.min.js" async></script>
<ai-caller-widget data-agent-id="your-id" data-api-key="your-key" />

That's it. No configuration objects, no initialization functions, no complex setup. Copy, paste, done.

What I'd Do Differently

If I were starting over, I'd probably use Shadow DOM for better style isolation. Right now, I'm relying on React's DOM management, which works fine, but Shadow DOM would give me complete isolation from the host page's styles.

I'd also think harder about bundle size from day one. It's easy to let it creep up when you're adding features, but every kilobyte matters when you're asking people to load your code on their site.

The Reality Check

Building embeddable widgets is weird. You're basically writing code that has to work in environments you've never seen, with constraints you don't control. It's like writing a library, but harder, because you can't make assumptions about the host environment.

The good news is that once you get the patterns right custom elements, proper cleanup, minimal dependencies it becomes manageable. The widget I built using these principles has been running on hundreds of sites for months without issues.

Just remember: your widget is a guest on someone else's site. Be polite, clean up after yourself, and don't break their stuff. That's really all there is to it.