Since its inception, the Lightning Web Components (LWC) framework has leveraged shadow DOM, a browser standard that provides encapsulation and composability for web components. To make shadow DOM work even better in LWC, we are now allowing access to the native capabilities provided by modern browsers.
For component authors, this change has the potential to improve the performance, capability, and compatibility of your LWC components. However, there may be some steps required to ensure that your components are ready for the new, native shadow DOM that we are introducing in Spring ’24.
Our plan to embrace native shadow DOM
To set the stage for the rest of this article, we’ll first give you a brief history lesson on why shadow DOM works the way it does in LWC.
The history of shadow DOM and LWC
As one of the first major web component frameworks, LWC was a pioneer in its use of shadow DOM. The rest of the web has been slowly catching up, and shadow DOM usage has been growing over the years. Many newer frameworks, such as Lit, Stencil, and FAST, have also embraced shadow DOM. But by being an early adopter, LWC took on several challenges.
When LWC debuted back in 2017, shadow DOM had poor support across Salesforce-supported browsers. To support legacy browsers, such as Internet Explorer 11, a synthetic (or polyfill) version of shadow DOM was needed. However, shadow DOM is not a simple feature that can be transparently polyfilled — it affects multiple aspects of the web platform, and some of its features are simply impossible to emulate.
Because of this, the LWC team created a partial polyfill, called synthetic shadow DOM. This polyfill covers most, but not all, of the shadow DOM standard. The differences between the two are small, but still significant enough that it would have been a burden on component authors to support different behavior for different browsers. To avoid this compatibility headache, LWC chose to serve the polyfill to all browsers, providing a consistent experience across the board.
Today, the situation is much different. With the end of support for IE11, all of the supported browsers in Lightning Experience now support shadow DOM. Strictly speaking, the polyfill is no longer necessary.
At the same time, browser vendors are constantly shipping new shadow DOM features, such as parts, constructible stylesheets, and imperative slots. These features would be difficult or impossible to emulate in the polyfill. Furthermore, our benchmarks have consistently shown that native shadow DOM is faster than synthetic shadow DOM, both in terms of JavaScript and CSS.
So, it’s time to sunset synthetic shadow DOM. But we know that not all components today will support native shadow DOM out of the box due to the minor inconsistencies mentioned above. So, how do we safely move the ecosystem to the more performant, standards-based solution?
Introducing shadow DOM mixed mode
With shadow DOM mixed mode, component authors can opt into native shadow DOM on a component-by-component basis. Use mixed shadow mode for a gradual migration rather than a disruptive all-or-nothing change.
To opt your component into native shadow DOM, use the shadowSupportMode static property.
1export class MyComponent extends LightningElement {
2 static shadowSupportMode = 'native';
3}Previously, the mixed shadow mode developer preview used the 'any' value. shadowSupportMode = 'any' is deprecated and superseded by shadowSupportMode = 'native'.
This component now runs using native shadow DOM mode. If you test your component and it continues to work fine with this change, then you’re all done! However, if you run into any errors, you’ll need to modify your component to properly support native shadow DOM.
Note that you may also run your Jest tests in native shadow DOM mode. However, this is not necessarily a guarantee that the components will work in practice since some issues (such as styling) are difficult or impossible to test in a headless environment like Jest.
Descendant components are forced into native mode
For the most part, the goal of shadow DOM mixed mode is to allow synthetic and native components to live side by side. However, in some cases, this is not possible.
In short, this is okay:
1<c-synthetic>
2 #shadow-root
3 <c-native></c-native>
4</c-synthetic>But this is not okay:
1<c-native>
2 #shadow-root
3 <c-synthetic></c-synthetic>
4</c-native>In other words, you cannot have a synthetic shadow component inside of a native shadow component.
Because of this, LWC will automatically force any shadow DOM-using components that are inside of a native shadow component into native mode. This includes children, grandchildren, etc. – all descendants.
1<c-native> <!-- opts into native using shadowSupportMode -->
2 #shadow-root
3 <c-child> <!-- forced into native, regardless of shadowSupportMode -->
4 #shadow-root
5 <c-grandchild> <!-- forced into native, regardless of shadowSupportMode -->
6 </c-grandchild>
7 </c-child>
8</c-native>This points to a good migration strategy: start with your leaf nodes. If you migrate your leaf nodes (i.e., your components without children) to native shadow first, then you will probably have a less rocky migration path.
Note that slots do not count as a parent-child relationship. The slotted content is owned by the component putting content into the slot, not the component that declares the <slot>. So for instance, this is valid:
1<!-- my-component.html -->
2<template>
3 <x-native> <!-- owned by my-component -->
4 <x-synthetic></x-synthetic> <!-- owned by my-component, slotted into x-native -->
5 </x-native>
6</template>In this case, the <x-synthetic> component will not be opted into native mode (unless some other ancestor is native).
Also, note that light DOM components are unaffected by this restriction. Light DOM components are compatible with both native shadow and synthetic shadow components.
The impact of native shadow DOM migration
Below, we’ll cover some of the more common differences between synthetic and native shadow DOM that may affect the migration process.
Not all base Lightning components support native shadow DOM
As you are migrating your components to native shadow DOM, it’s important to understand that not all base components are ready to support shadow DOM. We are on this journey with you, and our own components will need time to make the transition.
As such, you should be careful when migrating components that contain references to base Lightning components. For example:
1<template>
2 <lightning-button>Hello</lightning-button>
3</template><lightning-button> is a base Lightning component. In Spring ’24, base Lightning components don’t yet support native shadow DOM. If you attempt to migrate your own component to native shadow DOM, this will force components like <lightning-button> into native mode, and you may see incorrect styling or functional bugs.
As base Lightning components roll out with explicit support for native shadow DOM, we will announce the new capabilities in the release notes.
The rest of this guide assumes that your component tree contains only components you own.
Global styling
One of the biggest differences between native and synthetic shadow is with CSS. In synthetic shadow, styles cannot leak out, but they can leak in. Whereas in native shadow, styles cannot leak in either direction.
| Synthetic shadow | Native shadow | |
| Styles leak out? | ✅ No | ✅ No |
| Styles leak in? | ❌ Yes | ✅ No |
A common scenario that may cause problems is a global stylesheet at the top level of the page — for instance, an external .css file loaded as a static resource.
In the DOM, the CSS may look like this:
