Embedded chat widget
Jun 2, 2026
Peopled had a need to extract our core functionality (the RAG-powered chat system) to be embeddable in customer sites. This resulted in a need for two new large chunks of work:
- a UI chat widget in the form of a "FAB", that customers can place in their dashboards for their users
- an auth token system to authenticate these chat users, and make sure they receive org-specific information
- this project is discussed more in the auth service post
I owned the implementation of both projects end to end, but here I'll talk strictly on the chat module.
A central need for this feature was to be framework agnostic and as close to drop-in as possible, meaning developers needed to be able to plug this into any web framework of their choice (read: Vue, Angular, React). I decided to use web components as it's supported not by a library, but by every modern browser with reasonable backwards support.
The tech is a simple stack:
- Web Components, using Lit to keep myself sane. Vanilla web components would have meant that I'd have to manually handle re-renders, and I had no interest in that since Lit is so small as a dependency.
- CSS variables for thematic styling with our design system, allowing easy customer overrides.
- Typescript, end to end.
- Vite for the build tooling: library mode made this project a breeze.
markedMarkdown parser to show our custom charts and info cards. Out of the box,markedobviously has no idea what a "contact card" is, so I extended it with custom tokenizers that recognize our own markdown patterns and emit custom HTML tags, which then get upgraded into their corresponding Lit components (like a contact card) once they hit the DOM. Since this content is ultimately rendered into a page I don't control, I run everything through DOMPurify to make sure things get sanitized before rendering.- State managed as a singleton store, instead of adding something like Redux or Zustand. The state surface here is small, so I decided to keep things lean for the consumer.
The devops:
- This project is published via release-please based github actions
- Deployed to an S3 bucket, using a cdn to manage distribution