DEV Community

J Now
J Now

Posted on

Why I chose shadow DOM over an iframe for inline tooltips

The first prototype used an iframe overlay. Iframes sandbox cleanly, but they can't inherit the host page's fonts without a separate font-loading step — and even then you get FOUC on pages that load typefaces asynchronously. The tooltip flickered in and out of the correct weight on about half the sites I tested.

Shadow DOM fixes this: the tooltip renders inside the host document's layout context, so it inherits computed styles where I want them and resets everything else via :host encapsulation. No FOUC, no flash. The host page's CSS can't bleed into the shadow tree unless I explicitly allow it, so rabbitholes won't accidentally pick up a site's * { color: red } reset.

The tradeoff is z-index management. With an iframe you get a separate stacking context for free. With shadow DOM you're still inside the host's stacking context, which means a site that sets z-index: 9999 on a sticky nav can win. I handle this by reading getComputedStyle on any positioned ancestors of the tooltip mount point and nudging the shadow host's z-index above the highest value found. It works on ~95% of sites; the remaining edge cases are things like full-viewport overlays that own the entire stacking context.

The actual extension — rabbitholes — grew out of a different frustration: I kept hitting words and concepts in articles that I half-understood but not enough to follow the argument. Opening a new tab to look something up broke the read. Skipping past left me with a shallow version of whatever I was reading.

So I built a Chrome extension: highlight any text, get an inline explanation rendered next to your cursor via shadow DOM. Click any word in the explanation to go deeper. Drag across words to pick a phrase. Every answer surfaces two suggested rabbit-hole threads; a counter tracks how many hops deep you've gone. There's also a globe icon that re-answers the query enriched with Brave Search results, with clickable source chips.

Requests go directly from your browser to api.anthropic.com — no intermediary server, zero telemetry. Your Anthropic API key lives in chrome.storage.sync and never leaves the browser. Manifest V3.

https://github.com/robertnowell/rabbitholes

Top comments (0)