Better React Components Through Web Components

Alan Pearce
The Startup
Published in
4 min readOct 6, 2019
Photo by Margarida CSilva on Unsplash

As a UI Engineer, it often feels like we live in a React world. Sure there are other options out there; Angular and Vue to name a few, but it seems React is where it’s at for now and the foreseeable future. Creating and reusing React components is easy and convenient.

Web Components are another way to create reusable components by creating custom HTML elements, including their functionality and look and feel. They run natively in supported browsers; no library or framework required. For some reason, they have never really caught on, but there are aspects of them that can be used in React components with basically zero effort that can provide big benefits.

Custom Element Names

When creating a new component, most of the time we just use a good ole div or span for the container element we return, maybe adding a class to style or identify it. You could use a custom element instead. It has always been possible to create elements with whatever name you wanted and the browser would happily accept it. React is not quite that flexible. The custom elements spec states that a custom element name must have at least one hyphen in it. If it does not, the element will be an HTMLUnknownElement. When React is creating an element, it does a check to see if it is an HTMLUnknownElement and in dev mode throws a warning if it is. When you add a hyphen, most browsers will not make it an HTMLUnknownElement thus suppressing the warning. If your component is AwesomeWidget you could call your element awesome-widget.

Returning a custom element from your React component

This works in all browsers. Edge and Internet Explorer will always make the tag an HTMLUnknownElement so you will always get the warning, but it can be ignored. Full support in Edge should be coming soon.

Why bother? It makes it super easy to target your component from an external stylesheet.

Styling the custom element

It also makes the resulting markup cleaner and easier to understand. This can help with testing; if you need to target your component you can just use the custom element name instead of a class name. It can also help with debugging; if you are poking around in your rendered markup you can see your components much easier than if you just used a generic div or span.

If you want to get really fancy, you can register the element in the CustomElementRegistry:

Registering the custom element

First, check that the CustomElementRegistry exists, and then that the element has not already been defined. A custom element can only be defined once and an error will be thrown if you try to define it again. When you register a custom element you are telling the browser what the behavior of the element should be. Since the behavior here is handled by React, registering should be completely unnecessary.

Shadow DOM

Styling React components has often been contentious. We’ve tried inline styles, but quickly discovered their many pitfalls. External styles are not really coupled with the component. Now CSS-in-JS is all the rage. You generally write your styles right in the component, and they are then stripped out into a stylesheet with cryptic class names added to the selectors and elements in your component to scope them. You end up with something like this: <div class="ahsdg-dsad">...</div>

If you use the shadow DOM in your components, you can put your markup and styles in it and the styles will be automatically scoped by the browser to the markup. No cryptic class names necessary and you can keep your selectors concise. The styles do not leak so you don’t have to worry about overwriting other styles on the page, and most outside styles can’t pierce the shadow DOM to affect the markup.

Shameless Self Promotion

React shadow root allows you to easily add the shadow DOM to your React components.

Adding the shadow DOM

This will attach a shadow root to awesome-widget making it the shadow host for the shadow DOM. You don’t have to use a custom element for this but not all HTML elements can have a shadow root attached to them. All of the children of the ReactShadowRoot component will be moved in to the shadow DOM. Any children of awesome-widget not in ReactShadowRoot will not be rendered by the browser unless they are slotted, but they will still exist in the “light” DOM. Speaking of slots, they work as expected.

Attaching a shadow root is supported in all modern browsers except non-Chromiun Edge. You can check the static property shadowRootSupported for support and try to provide a fallback but none is provided out of the box.

React shadow root also includes support Constructable Stylesheets. When you put your styles in the the shadow DOM using the style element, you end up with styles repeated in your resulting markup if you have more that one instance of the same component in your page. Constructable Stylesheets lets the shadow root adopt the stylesheet so it doesn’t appear in the shadow DOM, but the styles are still applied appropriately. A single instance of the stylesheet can also be shared between multiple instances of the component. Currently supported in Chromium based browsers, you can check for support using the static property constructableStylesheetsSupported and easily provide a fallback by putting the styles directly in the shadow DOM.

Constructable Stylesheet with fallback

Using these two parts of Web Components in your React components can make your React components easier to style, test, and debug. Thoughts?

--

--