The widget inherits font from whatever page it's on. Shadow DOM doesn't stop that. Neither does it stop color, line-height or text-align.
It's a small draggable ball in the corner of every page. Click it, pick a game, press Space. Click again or hit Esc and it folds away, pausing the game with its state intact. You never open a new tab — so the widget has to coexist with global resets, !important rules, and frameworks with opinions about base styles.
What Shadow DOM stops
A rule targeting .button on the host page won't reach the markup inside my root, and mine won't leak out. That part works as advertised.
Inheritable properties still arrive, though. On a page that sets font-family: "Comic Sans MS" or a cramped base line-height, the widget picks it up. I reset the inheritable set explicitly on :host and put all: initial inside the root before any of my own styles.
Custom properties were the one I didn't expect. They inherit across the boundary by design, so a page with --primary or --radius will quietly restyle your widget if you use either name. Everything is namespaced now — --ita-primary and so on. all: unset on the host side changes nothing; the leak is on the way in.
Then there's the host element itself. It's still a div in someone else's DOM, so div, [class] and broad structural selectors can hit it. Keeping that surface to one element, no classes, styles only on :host, leaves very little to match.
!important from the host doesn't reach in. I test against a handful of real sites with aggressive resets anyway — @layer and constructable stylesheets are worth seeing in practice rather than reasoning about from the spec.
Forty tabs
The quieter failure mode is every open tab paying for a game nobody's playing. Games load on demand into iframes; the iframe is destroyed when you switch away or collapse the panel. State lives in chrome.storage, so teardown doesn't lose the round.
That leaves the content script with a host element, an iframe lifecycle and a drag handler. Nothing else.
One exception to pointer-events
Click-through mode stops the widget stealing a click from the page underneath. pointer-events: none on the shadow host looks like a one-line fix, except the drag handle still needs events — and children can't reliably opt back in with pointer-events: auto in every engine.
The host stays interactive. The inner surface is transparent to pointers, and the handle is the single explicitly interactive child. Otherwise the ball sits there and won't move.
The rest
The extension is completely free — no subscriptions and no account. A small sponsored banner sits on the game-selection screen; its SDK measures impressions and clicks anonymously, with no personal data and no third-party cookies. Widget position, theme and panel size stay local in chrome.storage. It doesn't read browsing history, page content or form data.
The install is heavier than I'd like — the emulator bundle dominates it. Lazy-loading that separately is on the list.
Two questions
Is Shadow DOM still the right call for injected content in 2026, or is there a cleaner isolation pattern I'm missing?
And how do others handle the inherited-property reset without shipping a giant all: initial block?
The extension is InTab Arcade if you want to poke at it.
Top comments (0)