A News Feed Web Component That Lives Inside Anyone Else's Website
An embeddable medical news aggregator shipped as a real, framework-agnostic HTML tag, themed per client through attributes, with an honest look at what actually kept the bundle lean and the one deliberate tradeoff that didn't.
Most of the frontend work I do lives inside its own app, its own domain, its own layout. This one doesn't. The RSS Feed Web Component is a medical and scientific news feed that gets dropped into someone else's website entirely, a hospital's patient portal, a publisher's sidebar, wherever a client wants their audience to see the latest articles from journals in their field, styled to match a page it has never seen before it loads.
Built for Lucidly and Axenso, a Rss Feed that distributes medical and scientific publishing content to its clients. I built the widget end to end on React and TypeScript.

#At a Glance
- Product type: embeddable RSS news aggregator, shipped as a native web component
- My role: frontend engineer, built the widget end to end
- Stack: React, TypeScript, Recoil, Chakra UI, Framer Motion, Embla Carousel, Axios, Tailwind
- Distribution: one script tag and one HTML attribute drop it into any client site, no iframe
- Content: live medical and scientific journal articles, organized by medical specialty
- Built for: Axenso
#The Problem: A React App That Isn't Allowed to Look Like One
A normal React app owns its own page. This widget doesn't get that luxury. It has to survive being planted inside someone else's HTML, next to someone else's CSS, on a domain it has no control over, and still come out looking like it was built for that exact page. That constraint shapes almost every decision below.

#Shipping React as an Actual HTML Tag
Instead of publishing this as a script that clients wire up by hand, the whole app compiles down into a real custom element, registered once as <rss-feed>. A client adds one tag to their page, gives it an API key, and the widget takes over from there, no build step, no React setup, no iframe sandboxing to fight with.
That's what makes "embed it anywhere" a real claim rather than a slogan. Custom elements are a browser standard, not a React feature, so the host page doesn't need to know React exists at all:
| Host environment | What it takes to embed |
|---|---|
| Plain HTML site | One script tag plus the widget's custom element |
| WordPress or another CMS page builder | Drop the element into an HTML block, no plugin needed |
| A React, Vue, or Angular app | Renders like any other DOM element, framework-agnostic |
| A site with its own design system | Widget styles stay scoped and don't leak out or collide |
Every visual option, the primary color, the highlighted text and background colors, whether the header icon shows, even the label text, is passed in as a plain HTML attribute rather than a React prop, since the thing consuming this tag might not be a React app, or any JavaScript framework, at all. It might just be a marketing site with a script tag pasted into the footer.
Landing inside someone else's CSS is its own problem. Every utility class the widget generates carries a tw- prefix, so a host page that happens to already define a class called .flex or .container never collides with anything the widget renders, and the widget's own styles never leak out and start affecting the host page either. It's a one-line config change, but it's the difference between "embeddable" and "embeddable until it lands on the wrong page."
#Bundle Size: What's Actually Small, and What Isn't
A widget that ships on other people's pages should be honest about its own weight, since every kilobyte is competing with whatever else that host page is already loading. A few real choices kept this one leaner than it would otherwise be:
- Icons load from storage, not the bundle. Medical specialty icons are fetched from a media URL at runtime rather than imported as build-time assets, so none of that artwork ever touches the JavaScript bundle.
- Icons and UI components are imported by name. Both
react-iconsand Chakra UI are pulled in as individual named imports,FaSpinner,Checkbox,Radio, rather than importing the whole library, so only what's actually used ends up in the output. - CSS ships purged. Tailwind only generates the utility classes the codebase actually references, which keeps the stylesheet around 30 KB regardless of how large the Tailwind class catalog itself is.
Where scope hoisting comes in: Parcel offers an optimization that combines modules together and strips out a lot of the wrapper code around each one. With it turned on, this project's bundle comes out to roughly 277 KB minified, around 93 KB gzipped, a tiny weight for a widget carrying its own UI library, animation library, and carousel.
#Theming Without a Backend Round Trip
Cube26, a different multi-tenant project I've worked on, resolves its theme by asking an API which colors belong to the current organization. This widget takes the opposite approach: the theme is the embed. A client sets their brand color directly as an attribute on the tag, the widget converts that hex value to its RGB channels in the browser, and writes it straight onto the page as a set of CSS custom properties that the rest of the styling reads from.
No API call, no loading state for the theme itself, no risk of a flash of the wrong colors while a config request resolves. The tradeoff is that changing a client's colors means updating the embed code on their site rather than flipping a setting in a dashboard, which is the right tradeoff for a widget that's meant to be dropped in once and mostly forgotten.

#One Widget, Scoped to Whichever Client Is Asking
Every request the widget makes carries the client's API key as an authentication header, and the backend uses that to scope every article, category, and subscription to that specific organization. There's no login, no session, nothing a site visitor ever sees. From a visitor's side it just looks like a news feed. From the API's side, every embed everywhere is quietly telling it exactly whose content to return.
#A Layout That Behaves Completely Differently on Mobile
On a wide screen, the widget shows three panes at once: medical specialty categories on the left, that category's articles in the middle, and the open article on the right, all visible together. On a narrow screen, there's only room for one of those at a time, so it becomes a guided flow instead, category list, then article list, then the article itself, one screen at a time with a back path built into the breadcrumb.
The same selection state drives both layouts. Nothing about which category or article is "selected" changes between screen sizes, only how many of those selections get shown on screen simultaneously. On mobile, opening an article outside the widget happens through a small external-link icon in the header instead of an inline link, since there usually isn't screen space left for anything but the reading pane itself.
#Getting People to Subscribe Without Being Pushy About It
The widget can optionally nudge first-time visitors into subscribing to an email digest of new articles. That prompt only ever appears once per browser, gated behind a cookie set the first time it's shown, so a returning visitor never gets nagged twice.
The subscription flow itself is a short wizard rather than one long form:
| Step | What happens |
|---|---|
| 1 | Pick which sources or specialties to follow |
| 2 | Choose a digest frequency, every two weeks or monthly |
| 3 | Enter an email address |
| 4 | Confirmation, with a small confetti animation on success |
Existing subscribers who reopen the same flow later see it in an update mode instead, editing their existing preferences rather than being walked through the same first-time pitch again.
#A Disclaimer Before Every External Link
Medical content carries real regulatory weight, so before a visitor is sent off to read the full article on its original journal site, the widget shows a short disclaimer: this is an external website, the platform isn't responsible for its content or accuracy, and medical guidance can vary by country. Visitors have to actively click through it to continue. It's a small modal, but it's doing real compliance work, and it's the kind of detail that's easy to skip if you're only thinking about the news-reading experience and not the legal one sitting underneath it.
#What Shipping This Actually Looked Like
None of the individual pieces here are hard on their own. A custom element wrapping a React app is a documented pattern. Hex-to-RGB is a few lines of math. A responsive layout that shows more or less at once depending on screen width is standard work. What made this project genuinely different from building a normal app was the constraint running underneath all of it: none of it gets to assume anything about the page it's landing on. Not the fonts around it, not the colors, not whether the visitor has ever seen the layout before, not even whether the host page is built with React at all, and not what other scripts happen to be running alongside it.
That's really the whole brief for a widget like this. Build something that behaves like a real product, subscribes people, respects compliance requirements, adapts to any screen, stays visually and technically out of the way of whatever page it's sitting on, and makes deliberate tradeoffs, including ones that cost bundle size, when reliability on someone else's page is the thing actually on the line.