stitches
stitches copied to clipboard
Stitches 0.2 - insertionMethod alternative for dynamically created <iframe> and shadow DOM
In Stitches 0.2, the insertionMethod
is removed because of the transition to CSSOM. With that change, I propose some alternative API's that would grant the ability to initialize the root dom node dynamically / lazily.
- Use stitches styled components within dynamically created iframes and shadow doms
- can isolate styles using separate
createCss()
for different iframes / shadow doms
Taking a look at the source code for Stitches 0.2, It seems that createCss({root: DOCUMENT})
:
is an undocumented api for applying a custom root. However, this does not apply when the root is lazily / dynamically created.
Use Case
Building a chrome extension with UI overlay. A shadowdom/ iframe is dynamically created to separate css / dom implementation to avoid clashing.
Here are some API
Method 1: Similar to emotion's CacheProvider
//Emotion API for inspiration
<CacheProvider value={createCache({container: RootNode})}>
const { styled, RootProvider } = createCss({})
const Box = styled('div', {})
const IFrameExample = () => {
const ref = useRef()
return (
<iframe ref={ref}>
<RootProvider root={ref.current.contentWindow.document}>
{/* Simplified for example, uses portals to insert components into body */}
<Box>Example</Box>
</RootProvider>
</iframe>
)
}
Method 2: Dynamically call initRoot
const { styled, initRoot } = createCss({})
const Box = styled('div', {})
const IFrameExample = () => {
const ref = useRef()
useEffect(() => initRoot(ref.current.contentWindow.document), [])
return (
<iframe ref={ref}>
{/* Simplified for example, uses portals to insert components into body */}
<Box>Example</Box>
</iframe>
)
}
This seems related to #395.
If you could scope a style sheet to a custom root (a given element in the react tree), then you'd be able to solve scoping issue for iframes and SSR (one sheet per request) at the same time.
While I like being explicit as in these two api proposals, perhaps we could just detect if we're in a shadow DOM or iframe and apply styles to the root there, adding :host
, injecting the style tag into the shadow root, etc.
Just in case it can give some inspiration, I'm not proud of this (I hoped it's temporary), but basically I provide an "env variable" when building my extension with the id of the element to insert the styles into: https://github.com/LexSwed/fxtrot-ui/blob/master/src/lib/stitches.config.ts#L201
Any updates if this will be implemented in future iterations of v1?
Any updates if this will be implemented in future iterations of v1?
It was mentioned on Discord that it will be one of the next features to work on after v1 is released, to get more context and understand how this is used, to provide the best solution. I'm sure they gonna publish a roadmap here when possible issues with v1 are mitigated
Iām very interested in this feature, as it would also answer our web components discussion, which also happens to be the highest voted discussion.
Adding one of my own workaround here as we ended up encountering this issue at Tango when attempting to integrate our Stitches based Design System to our chrome extension:
- First issue: Our extension runs in an iframe and we first ended up with unstyled component. The reason behind this was that stitches "didn't know" where to inject the styles. We noticed that by calling
getCssText
and injecting the output in a dedicated
So we created this hook, it's not too ugly, not the most performant piece of code I'm proud of, but hey, it works š:
import { getCssText } from 'stitches.config';
import { useLayoutEffect } from 'react';
export const useInjectStyles = () => {
useLayoutEffect(() => {
const styleTag = document.getElementById('iframe-id').contentWindow.document.getElementById('stitches');
styleTag!.innerHTML = getCssText();
});
};
- Second issue: That on its own worked well. However, we have a specific use case: our extension can be injected on any webpage, and the solution fell apart when we tried to run our extension on pages of websites that were build with Stitches.
getCssText
failed to output the styles when ran in this specific use case, and the reason behind that (I think) was because it was conflicting with some of the website's own stitches dependency (not sure exactly what, but that's what I deducted)
To fix this, I dug a bit on the Stitches codebase and noticed the mention of a root
option for the createStitches
function: https://github.com/modulz/stitches/blob/0ebaf9f988871ac0d8d5f2b72f2a8042e0d1b56f/packages/core/src/createStitches.js#L25
I went in and tried it out on my Design System's stitches config by setting the root to the <style id="stitches"/>
tag:
export const { styled } = createStitches({
//@ts-ignore
root: document.getElementById('stitches')!,
})
This option is not defined in the type declaration of the package but it's definitely there (I just ts-ignored it for now) and that made everything work on our extension and web app alike. No real impact, although this is pretty hacky and I'd rather have an official support than having to make sure this doesn't break during the next update š
BONUS:
For Next.js support you'll have to make sure to handle SSR by setting the root
option as follow:
export const { styled } = createStitches({
//@ts-ignore
root: typeof window !== 'undefined' ? document.getElementById('stitches')! : null,
})
Pending the merge and release of this PR: https://github.com/modulz/stitches/pull/1004
Here is an example using @stitches
with web components and ShadowDOM.
https://codesandbox.io/s/circular-json-reproduction-forked-gy01cx?file=/src/index.tsx
any updates on this PR ?