Skip to content

Commit 2615e29

Browse files
committed
docs: Add section to FAQ about teleports/portals
1 parent 8ca176d commit 2615e29

File tree

1 file changed

+132
-42
lines changed

1 file changed

+132
-42
lines changed

docs/guide/resources/faq.md

Lines changed: 132 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -28,47 +28,137 @@ See https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data
2828

2929
## My component library doesn't work in content scripts!
3030

31-
Component libraries place their CSS in the document's `<head>` by default. When using `createShadowRootUi`, your UI is isolated from the document's styles because it's inside a ShadowRoot.
32-
33-
To fix this, you need to tell your component library to insert it's CSS inside the shadow root. Here's the docs for a couple of popular libraries:
34-
35-
- React
36-
- Ant Design: [`StyleProvider`](https://ant.design/docs/react/compatible-style#shadow-dom-usage)
37-
- Mantine: [`MantineProvider#getRootElement` and `MantineProvider#cssVariablesSelector`](https://mantine.dev/theming/mantine-provider/)
38-
39-
> If your library isn't listed above, try searching it's docs/issues for "shadow root", "shadow dom", or "css container".
40-
41-
`createShadowRootUi` provides it's own `<head>` element inside the shadow root, so that were you should tell the library to add the CSS. Here's an example with Ant Design:
42-
43-
```tsx
44-
import { StyleProvider } from '@ant-design/cssinjs'; // [!code ++]
45-
import ReactDOM from 'react-dom/client';
46-
import App from './App.tsx';
47-
48-
const ui = await createShadowRootUi(ctx, {
49-
name: 'example-ui',
50-
position: 'inline',
51-
anchor: 'body',
52-
onMount: (container) => { // [!code --]
53-
onMount: (container, shadow) => { // [!code ++]
54-
const cssContainer = shadow.querySelector("head")!; // [!code ++]
55-
const root = ReactDOM.createRoot(container);
56-
root.render(
57-
<StyleProvider container={cssContainer}> // [!code ++]
58-
<App />
59-
</StyleProvider> // [!code ++]
60-
);
61-
return root;
62-
},
63-
onRemove: (root) => {
64-
root?.unmount();
65-
},
66-
});
67-
```
31+
This is usually caused by one of two things (or both) when using `createShadowRootUi`:
32+
33+
1. Styles are added outside the `ShadowRoot`
34+
35+
:::details
36+
Some component libraries manually add CSS to the page by adding a `<style>` or `<link>` element. They place this element in the document's `<head>` by default. This causes your styles to be placed outside the `ShadowRoot` and it's isolation blocks the styles from being applied to your UI.
6837

69-
Note that this doesn't effect all component libraries, just ones that inject CSS themselves rather than having you import their CSS. This approach is more prevailent in the React community, but not limited to it. That's why only React libraries are listed above. Vuetify, for example, works just fine because you import its CSS - WXT picks up on this and the CSS is added inside the shadow root automatically:
38+
When a library does this, **you need to tell the library where to put its styles**. Here's the documentation for a few popular component libraries:
39+
40+
- Ant Design: [`StyleProvider`](https://ant.design/docs/react/compatible-style#shadow-dom-usage)
41+
- Mantine: [`MantineProvider#getRootElement` and `MantineProvider#cssVariablesSelector`](https://mantine.dev/theming/mantine-provider/)
42+
43+
> If your library isn't listed above, try searching it's docs/issues for "shadow root", "shadow dom", or "css container". Not all libraries support shadow DOMs, you may have to open an issue to request this feature.
44+
45+
Here's an example of configuring Antd's styles:
46+
47+
```tsx
48+
import { StyleProvider } from '@ant-design/cssinjs';
49+
import ReactDOM from 'react-dom/client';
50+
import App from './App.tsx';
51+
52+
const ui = await create`ShadowRoot`Ui(ctx, {
53+
// ...
54+
onMount: (container, shadow) => {
55+
const cssContainer = shadow.querySelector('head')!;
56+
const root = ReactDOM.createRoot(container);
57+
root.render(
58+
<StyleProvider container={cssContainer}>
59+
<App />
60+
</StyleProvider>,
61+
);
62+
return root;
63+
},
64+
});
65+
```
7066

71-
```ts
72-
import 'vuetify/styles'; // <-- This line imports the CSS, just like importing a .css file
73-
import { createVuetify } from 'vuetify';
74-
```
67+
:::
68+
69+
2. UI elements are added outside the `ShadowRoot`
70+
71+
::::::details
72+
This is mostly caused by `Teleport` or `Portal` components that render an element somewhere else in the DOM, usually in the document's `<body>`. This is usually done for dialogs or popover components. This renders the element is outside the `ShadowRoot`, so styles are not applied to it.
73+
74+
To fix this, **you need to both provide a target to your app AND pass the target to the `Teleport`/`Portal`**.
75+
76+
First, store the reference to the `ShadowRoot`'s `<body>` element (not the document's `<body>`):
77+
78+
:::code-group
79+
80+
```ts [Vue]
81+
import { createApp } from 'vue';
82+
import App from './App.vue';
83+
84+
const ui = await create`ShadowRoot`Ui(ctx, {
85+
// ...
86+
onMount: (container, shadow) => {
87+
const teleportTarget = shadow.querySelector('body')!;
88+
const app = createApp(App)
89+
.provide('TeleportTarget', teleportTarget)
90+
.mount(container);
91+
return app;
92+
},
93+
});
94+
ui.mount();
95+
```
96+
97+
```tsx [React]
98+
// hooks/PortalTargetContext.ts
99+
import { createContext } from 'react';
100+
101+
export const PortalTargetContext = createContext<HTMLElement>();
102+
103+
// entrypoints/example.content.ts
104+
import ReactDOM from 'react-dom/client';
105+
import App from './App.tsx';
106+
import PortalTargetContext from '~/hooks/PortalTargetContext';
107+
108+
const ui = await create`ShadowRoot`Ui(ctx, {
109+
// ...
110+
onMount: (container, shadow) => {
111+
const portalTarget = shadow.querySelector('body')!;
112+
const root = ReactDOM.createRoot(container);
113+
root.render(
114+
<PortalTargetContext.Provider value={portalTarget}>
115+
<App />
116+
</PortalTargetContext.Provider>,
117+
);
118+
return root;
119+
},
120+
});
121+
ui.mount();
122+
```
123+
124+
:::
125+
126+
Then use the reference when teleporting/portaling part of your UI to a different place in the DOM:
127+
128+
:::code-group
129+
130+
```vue [Vue]
131+
<script lang="ts" setup>
132+
import { Teleport } from 'vue';
133+
134+
const teleportTarget = inject('TeleportTarget');
135+
</script>
136+
137+
<template>
138+
<div>
139+
<Teleport :to="teleportTarget">
140+
<dialog>My dialog</dialog>
141+
</Teleport>
142+
</div>
143+
</template>
144+
```
145+
146+
```tsx [React]
147+
import { useContext } from 'react';
148+
import { createPortal } from 'react-dom';
149+
import PortalTargetContext from '~/hooks/PortalTargetContext';
150+
151+
const MyComponent = () => {
152+
const portalTarget = useContext(PortalTargetContext);
153+
154+
return <div>{createPortal(<dialog>My dialog</dialog>, portalTarget)}</div>;
155+
};
156+
```
157+
158+
:::
159+
160+
::::::
161+
162+
Both issues have the same cause: the library puts something outside the `ShadowRoot`, and the `ShadowRoot`'s isolation prevents CSS from being applied to your UI.
163+
164+
Both issues have the same fix: tell the library to put elements inside the `ShadowRoot`, not outside it. See the details above for more information and example fixes for each problem.

0 commit comments

Comments
 (0)