10

I'm trying to use https://material-ui.com/ components inside shadow dom, and need a way to inject those styles inside shadow dom. by default material-ui, which uses jss under the hood injects styles in the head of the page.

Is that even possible? Can anyone come with an example?

bboydflo
  • 907
  • 1
  • 15
  • 24
  • I don't think injecting React and the whole shebang into the page is a good idea, it certainly doesn't seem so to me. The usual approach is to insert just one iframe that points to an html file in your extension (listed in web_accessible_resources) that is a standard page which can do whatever it wants and load any material UI components inside. I've seen at least one full example/tutorial for that on the web. – wOxxOm Jan 30 '19 at 04:41
  • Do you want the styles of a component into it's shadow DOM or would you be ok to put all styles into a single shadow DOM? – Sebastian Jan 30 '19 at 07:30
  • @wOxxOm I completely agree, but the iframe approach it's a bit tricky in my experience because of few things like focus (when you click inside an iframe is like being in another page) and resizing of the iframe (i've had issues with that when for example I put a dropdown inside the iframe I need to tell the outside page to resize the iframe so the content is visible). a web component will definitely be more native. "epsilon" what I would like to do is to make a single web component using few material-ui widgets, and their style should be injected in the shadow root. – bboydflo Jan 31 '19 at 06:50
  • Even if you able to archive your goal you might get a problem with displaying popup material components from inside your shadow-root. Because seems material library insert popups dom elements directly into html page body which will be styleless in your case. – Andrey Luzinov Apr 02 '20 at 18:34

4 Answers4

19

This is what my web component looks like, it is a web component that renders a react app that contains material-ui styles.

import * as React from 'react';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import { create } from 'jss';

import { App } from '@myApp/core';

class MyWebComponent extends HTMLElement {
  connectedCallback() {
    const shadowRoot = this.attachShadow({ mode: 'open' });
    const mountPoint = document.createElement('span');
    const reactRoot = shadowRoot.appendChild(mountPoint);
    const jss = create({
      ...jssPreset(),
      insertionPoint: reactRoot
    });

    render(
      <StylesProvider jss={jss}>
        <App />
      </StylesProvider>,
      mountPoint
    );
  }
}
customElements.define('my-web-commponent', MyWebComponent);

Setting the insertionPoint on jss to the actual react root inside the shadow root will tell jss to insert those styles inside that shadow root.

Shawn Mclean
  • 56,733
  • 95
  • 279
  • 406
  • This works with many components, however if you use the Dialog, its tags are placed in the body and this trick doesn't work. – AGPX Aug 05 '20 at 09:25
13

Using https://github.com/Wildhoney/ReactShadow to create shadow dom (you could also do it by hand as shown in previous answer), I created a small WrapperComponent that encapsulates the logic.

import root from 'react-shadow';
import {jssPreset, StylesProvider} from "@material-ui/styles";
import {create} from 'jss';
import React, {useState} from "react"

const WrappedJssComponent = ({children}) => {
  const [jss, setJss] = useState(null);
  
  function setRefAndCreateJss(headRef) {
    if (headRef && !jss) {
      const createdJssWithRef = create({...jssPreset(), insertionPoint: headRef})
      setJss(createdJssWithRef)
    }
  }
  
  return (
    <root.div>
      <head>
        <style ref={setRefAndCreateJss}></style>
      </head>
      {jss &&
      <StylesProvider jss={jss}>
        {children}
      </StylesProvider>
      }
    
    </root.div>
  )
}

export default WrappedJssComponent

Then you just need to Wrap your app, or the part of your app you want to shadow inside <WrappedJssComponenent><YourComponent></YourComponent></WrappedJssComponenent>.

Be careful, some of the material-UI component won't work as usual (I had some trouble with

  • ClickAwayListener, maybe because it uses the parent dom, did not investigate more than that to be honest.
  • Popper, and everything that will try to use document.body as container will not have access to jss defined in shadow node. You should give an element inside the shadow dom as container.
Liran H
  • 9,143
  • 7
  • 39
  • 52
Robin Leclerc
  • 131
  • 1
  • 3
1

There is also a whole page in the docs now (MaterialUI 5) that covers how to integrate MUI with a shadow-dom. You also might have to set Portal defaults not to target the dom. https://mui.com/material-ui/guides/shadow-dom/

Adrian
  • 46
  • 4
  • 1
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/33641176) – Tom Jan 20 '23 at 20:10
0

When using @material-ui/core/CssBaseline with MUI, also emotion styles are being used. In order to support both legacy jss and emotion you can extend the accepted answer above with a CacheProvider like this:

import ReactDOM from 'react-dom/client'
import App from './App'
import createCache from '@emotion/cache'
import { CacheProvider } from '@emotion/react';
import { StylesProvider, jssPreset } from '@material-ui/styles';
import { create } from 'jss';

class ReportComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    const mountPoint = document.createElement('div');
    const emotionPoint = this.shadowRoot!.appendChild(document.createElement('div'));
    const emotionCache = createCache({
      key: 'report-component',
      container: emotionPoint
    });

    const reactRoot = this.shadowRoot!.appendChild(mountPoint);
    const root = ReactDOM.createRoot(reactRoot);
    const jss = create({
      ...jssPreset(),
      insertionPoint: reactRoot
    });

    root.render(
      <StylesProvider jss={jss}>
        <CacheProvider value={emotionCache}>
          <App />
        </CacheProvider>
      </StylesProvider>
    );
  }
}
customElements.define('report-component', ReportComponent);
Peter Salomonsen
  • 5,525
  • 2
  • 24
  • 38