Summary
There is no public API to make styled / css / createGlobalStyles inject their generated <style> into a document other than the main document.head. styled-components solves this with <StyleSheetManager target={node}>. solid-styled-components has no equivalent, which breaks any multi-document rendering:
- Electron child
BrowserWindow rendered via <Portal mount={childWin.document.body}> — the child document never receives the goober sheet, so all styled components render unstyled there.
iframe content portals.
- Shadow DOM roots.
- Pop-out windows opened with
window.open.
Why this is almost already supported
The internals already thread a target through to goober:
// src/index.js — makeStyled
function makeStyled(tag) {
let _ctx = this || {};
return (...args) => {
const Styled = props => {
...
let className = css.apply(
{ target: _ctx.target, o: append, p: withTheme, g: _ctx.g },
args
);
...
goober's css/glob honor this.target and write the sheet into that node's root (document.head or a shadow/other-document root). But styled is exported as:
export const styled = new Proxy(makeStyled, {
get(target, tag) { return target(tag); } // makeStyled called with `this === undefined`
});
so _ctx.target is always undefined → everything lands in the main document.head. There is no way for a consumer to set it.
Reproduction
import { render, Portal } from "solid-js/web";
import { styled } from "solid-styled-components";
const Box = styled.div`background: tomato; width: 100px; height: 100px;`;
// open a second window/document
const win = window.open("about:blank")!;
render(() => (
<Portal mount={win.document.body}>
<Box /> {/* renders, but the .go<hash> rule is injected into the MAIN
document's <style>, never into win.document → unstyled */}
</Portal>
), document.getElementById("root")!);
The <div class="go…"> appears in win.document, but the matching CSS rule is only in the opener document's <head>, so the box is unstyled in the child window.
keyframes and glob/createGlobalStyles have the same limitation (they also inject into the default sheet).
Proposed API
Mirror styled-components:
import { StyleSheetManager } from "solid-styled-components";
<Portal mount={win.document.body}>
<StyleSheetManager target={win.document.head}>
<App />
</StyleSheetManager>
</Portal>
All styled / css.class / createGlobalStyles rendered inside the provider inject into target instead of document.head.
Sketch
A context carrying the target, read inside Styled (and the createGlobalStyles component), passed through to the existing css.apply({ target, … }) call:
const StyleTargetContext = createContext();
export function StyleSheetManager(props) {
return createComponent(StyleTargetContext.Provider, {
value: props.target,
get children() { return props.children; }
});
}
// inside Styled():
const target = useContext(StyleTargetContext) ?? _ctx.target;
let className = css.apply({ target, o: append, p: withTheme, g: _ctx.g }, args);
Because goober already routes by target, this is a small, backward-compatible change (no behavior change when no provider is present).
Workaround today
We currently mirror every <style>/<link> from the opener document.head into the child window's head with a MutationObserver, and additionally shim keyframes so @keyframes bodies aren't dropped. It works but is fragile and duplicates the whole sheet per window.
Offer
Happy to send a PR implementing StyleSheetManager (context-threaded target) plus keyframes/glob target support, with tests. Filing this first to confirm the API shape (<StyleSheetManager target> vs a setup-level option) you'd prefer.
Summary
There is no public API to make
styled/css/createGlobalStylesinject their generated<style>into a document other than the maindocument.head.styled-componentssolves this with<StyleSheetManager target={node}>.solid-styled-componentshas no equivalent, which breaks any multi-document rendering:BrowserWindowrendered via<Portal mount={childWin.document.body}>— the child document never receives the goober sheet, so allstyledcomponents render unstyled there.iframecontent portals.window.open.Why this is almost already supported
The internals already thread a
targetthrough to goober:goober's
css/globhonorthis.targetand write the sheet into that node's root (document.heador a shadow/other-document root). Butstyledis exported as:so
_ctx.targetis alwaysundefined→ everything lands in the maindocument.head. There is no way for a consumer to set it.Reproduction
The
<div class="go…">appears inwin.document, but the matching CSS rule is only in the opener document's<head>, so the box is unstyled in the child window.keyframesandglob/createGlobalStyleshave the same limitation (they also inject into the default sheet).Proposed API
Mirror
styled-components:All
styled/css.class/createGlobalStylesrendered inside the provider inject intotargetinstead ofdocument.head.Sketch
A context carrying the target, read inside
Styled(and thecreateGlobalStylescomponent), passed through to the existingcss.apply({ target, … })call:Because goober already routes by
target, this is a small, backward-compatible change (no behavior change when no provider is present).Workaround today
We currently mirror every
<style>/<link>from the openerdocument.headinto the child window's head with aMutationObserver, and additionally shimkeyframesso@keyframesbodies aren't dropped. It works but is fragile and duplicates the whole sheet per window.Offer
Happy to send a PR implementing
StyleSheetManager(context-threadedtarget) pluskeyframes/globtarget support, with tests. Filing this first to confirm the API shape (<StyleSheetManager target>vs asetup-level option) you'd prefer.