Using Chakra UI in Shadow DOM
A guide for installing Chakra UI with Shadow DOM
When developing extensions for browsers or using Chakra as part of a large project, leveraging the Shadow DOM is useful for style and logic encapsulation.
Template
Use the following template to get started quickly
Installation
Install dependencies
npm i @chakra-ui/react @emotion/react @emotion/cache react-shadow
The additional packages used are:
react-shadow
used to create a Shadow DOM easily@emotion/cache
used to create a custom insertion point for styles
Add snippets
Snippets are pre-built components that you can use to build your UI faster.
Using the @chakra-ui/cli
you can add snippets to your project.
npx @chakra-ui/cli snippet add
Update tsconfig
If you're using TypeScript, you need to update the compilerOptions
in the
tsconfig file to include the following options:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler",
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
Configure style engine
Create a system.ts
file in the root of your project and configure the style
engine.
components/ui/system.ts
import { createSystem, defaultConfig, defineConfig } from "@chakra-ui/react"
const varRoot = ":host"
const config = defineConfig({
cssVarsRoot: varRoot,
conditions: {
light: `${varRoot} &, .light &`,
},
preflight: { scope: varRoot },
globalCss: {
[varRoot]: defaultConfig.globalCss?.html ?? {},
},
})
export const system = createSystem(defaultConfig, config)
Setup provider
Update the generated components/ui/provider
component with the Provider
component.
This provider composes the following:
ChakraProvider
from@chakra-ui/react
for the styling systemEnvironmentProvider
fromreact-shadow
to ensure Chakra components query the DOM correctlyCacheProvider
from@emotion/react
to provide the custom insertion pointThemeProvider
fromnext-themes
for color mode
components/ui/provider.tsx
"use client"
import { ChakraProvider, EnvironmentProvider } from "@chakra-ui/react"
import createCache from "@emotion/cache"
import { CacheProvider } from "@emotion/react"
import { ThemeProvider, type ThemeProviderProps } from "next-themes"
import { useEffect, useState } from "react"
import root from "react-shadow/emotion"
import { system } from "./system"
export function Provider(props: ThemeProviderProps) {
const [shadow, setShadow] = useState<HTMLElement | null>(null)
const [cache, setCache] = useState<ReturnType<typeof createCache> | null>(
null,
)
useEffect(() => {
if (!shadow?.shadowRoot || cache) return
const emotionCache = createCache({
key: "root",
container: shadow.shadowRoot,
})
setCache(emotionCache)
}, [shadow, cache])
return (
<root.div ref={setShadow}>
{shadow && cache && (
<EnvironmentProvider value={() => shadow.shadowRoot ?? document}>
<CacheProvider value={cache}>
<ChakraProvider value={system}>
<ThemeProvider {...props} />
</ChakraProvider>
</CacheProvider>
</EnvironmentProvider>
)}
</root.div>
)
}
Use the provider
Wrap your application with the Provider
component generated in the
components/ui/provider
component at the root of your application.
src/main.tsx
import { Provider } from "@/components/ui/provider"
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import App from "./App.tsx"
createRoot(document.getElementById("root")!).render(
<StrictMode>
<Provider>
<App />
</Provider>
</StrictMode>,
)
Enjoy!
With the power of the snippets and the primitive components from Chakra UI, you can build your UI faster.
import { Button } from "@/components/ui/button"
import { HStack } from "@chakra-ui/react"
export default function App() {
return (
<HStack>
<Button>Click me</Button>
<Button>Click me</Button>
</HStack>
)
}