Skip to main content

Adding Material UI in a Browser Extension Content Page

ยท 4 min read
Ziinc

Building great UI elements are hard, and after working in the front end space for so long, I've come to appreciate component libraries that let you get up and running at lightning speed.

So of course, I have come to realise and appreciate how steadfast and solid the Material UI library is. Granted, not everyone likes the design of Material UI out of the box, but the ability to not have to integrate and merge stylesheets from disparate tools is quite freeing.

In any case, lets get back to the meat of today's discussion, adding Material UI to a browser extension.

Browser Extensions, React, and the Shadow DOMโ€‹

When injecting your own components into a page, you would most definitely want to use a Shadow DOM so that your stylesheets do not intefere with the main pages' stylesheets. The chances that your chosen classes are identical is rare but not impossible, and we want to make the impossible impossible, so we do it the right way: with the Shadow DOM.

MDN has a great article on the Shadow DOM, so the gist of it (in the most Singaporean way that I can explain it): the DOM is same-same, but different.

The Shadow DOM works "in the shadows" without clashing with the main page, letting us attach stylesheets and components to it, while still being visible to the user. Just like a separate workspace to do our UI magic with!

The usual way to deal with the Shadow DOM with an browser extension is to add a main container component (like a div), and then attaching a stylesheet to the container (so that it only applies to the container and its children).

So, sparing you the details and jumping straight to code, the end result is something like this:

const appContainer = document.createElement("div");
const shadowRoot = appContainer.attachShadow({ mode: "closed" });
const styleEl = document.createElement("link");
styleEl.setAttribute("rel", "stylesheet");
styleEl.setAttribute("href", "path/to/my/stylesheet.css");
shadowRoot.appendChild(styleEl);

When mode is set to closed, the shadow DOM will not be accessible to via JS in the main page context.

So the gist of the above is that we:

  1. Make a container
  2. Add a shadow DOM to it
  3. Attach a stylesheet to the shadow DOM

Give Me Emotion!โ€‹

Material UI uses Emotion as its styling solution for the components. As such, we will need to perform Emotion's setup onto the Shadow DOM, as it would not automatically set up the CSS injection caching in the Shadow DOM, where our components are located.

Show me the code!! Alright I hear you, have a look at this:

const emotionRoot = document.createElement("div");

shadowRoot.appendChild(emotionRoot);
const cache = createCache({
key: "css",
container: emotionRoot,
});

Firstly, we create an element and attach it to the shadowRoot, so it is now a child of the Shadow DOM. We create a cache using createCache so that we can customize the way that Emotion sets up the cache, and specify the emotionRoot as the container where Emotion will append all <style> tags to.

MUI documentation has a rather verbose example to get this to work, so I have slimmed this down quite a bit to the bare minimum needed.

Our MUI components must also be a child of the Shadow DOM, so we will need to render our application as so:


let appRoot = document.createElement("div");
shadowRoot.appendChild(appRoot);
document.body.appendChild(appContainer);

ReactDOM.createRoot(appRoot).render(
<React.StrictMode>
<CacheProvider value={cache}>
<App />
</CacheProvider>
</React.StrictMode>
);

Don't forget to set the <CacheProvider /> with the value prop!

Finally, tweak your theme to use the Shadow DOM root element, so that portals work correctly and are portaled into the Shadow DOM and not the main page. The setup is on the MUI docs.

And voila, you have MUI injected into the page!