Documents
Home>Documents>AI>Agent>Xgen

Building an Embeddable Chatbot Widget for External Sites

6 min readJul 1, 2025Feb 22, 2026

A Chatbot You Can Embed Anywhere

We built a widget that embeds XGen AI workflows as a chatbot on external websites. Drop in a single script tag and any site gets an AI chatbot.

Embed Widget Architecture

The widget is built as a self-contained bundle and loaded via a <script> tag. Shadow DOM isolates it from the host site's CSS so there are no style conflicts.

<!-- 외부 사이트에 삽입하는 코드 -->
<script
  src="https://widget.xgen.plateer.com/embed.js"
  data-workflow-id="wf_abc123"
  data-theme="light"
  data-position="bottom-right"
  async
></script>

Shadow DOM Isolation

class XGenChatWidget extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'closed' });
    
    // 스타일 주입
    const style = document.createElement('style');
    style.textContent = widgetStyles;
    shadow.appendChild(style);
    
    // React 앱 마운트
    const container = document.createElement('div');
    shadow.appendChild(container);
    
    const root = createRoot(container);
    root.render(
      <ChatWidget
        workflowId={this.getAttribute('data-workflow-id')!}
        theme={this.getAttribute('data-theme') as 'light' | 'dark'}
      />
    );
  }
}

customElements.define('xgen-chat', XGenChatWidget);

Widget UI Layout

The widget starts as a floating button that can be expanded or collapsed. Clicking it opens the chat window with an animation.

const ChatWidget: React.FC<WidgetProps> = ({ workflowId, theme }) => {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <div className="fixed bottom-4 right-4 z-50">
      {isOpen && (
        <div className="mb-4 w-96 h-[500px] rounded-2xl shadow-2xl overflow-hidden">
          <ChatHeader onClose={() => setIsOpen(false)} />
          <ChatMessages workflowId={workflowId} />
          <ChatInput />
        </div>
      )}
      <FloatingButton
        onClick={() => setIsOpen(!isOpen)}
        isOpen={isOpen}
      />
    </div>
  );
};

Customization Options

The widget supports a range of customization: color theme, position, initial message, avatar image, and more.

Bundle Optimization

The embed script needs to be as small as possible. We swapped React for Preact and applied tree shaking to bring the final bundle down to roughly 45 KB (gzip). To avoid impacting host-site performance, loading is deferred with async + requestIdleCallback.

CORS and Security

Because the widget calls the XGen API from external sites, CORS configuration matters. Workflow owners specify an allowlist of domains, and API calls are restricted to those domains only.

Tags
chatbotembed widgetShadow DOMWeb Componentbundle optimization