Working with Shadow DOM
Lit components use shadow DOM to encapsulate their DOM. Shadow DOM provides a way to add a separate isolated and encapsulated DOM tree to an element. DOM encapsulation is the key to unlocking interoperability with any other code—including other web components or Lit components—functioning on the page.
Shadow DOM provides three benefits:
- DOM scoping. DOM APIs like
document.querySelectorwon't find elements in the component's shadow DOM, so it's harder for global scripts to accidentally break your component.
- Style scoping. You can write encapsulated styles for your shadow DOM that don't affect the rest of the DOM tree.
- Composition. The component's shadow root, which contains its internal DOM, is separate from the component's children. You can choose how children are rendered in your component's internal DOM.
For more information on shadow DOM:
Older browsers. On older browsers where native shadow DOM isn't available, the web components polyfills may be used. Please note that Lit's
polyfill-support module must be loaded along with the web components polyfills. See Requirements for legacy browsers for details.
Accessing nodes in the shadow DOMPermalink to “Accessing nodes in the shadow DOM”
Lit renders components to its
renderRoot, which is a shadow root by default. To find internal elements, you can use DOM query APIs, such as
renderRoot should always be either a shadow root or an element, which share APIs like
You can query internal DOM after component initial render (for example, in
firstUpdated), or use a getter pattern:
LitElement supplies a set of decorators that provide a shorthand way of defining getters like this.
@query, @queryAll, and @queryAsync decoratorsPermalink to “@query, @queryAll, and @queryAsync decorators”
@queryAsync decorators all provide a convenient way to access nodes in the internal component DOM.
@queryPermalink to “@query”
Modifies a class property, turning it into a getter that returns a node from the render root. The optional second argument when true performs the DOM query only once and caches the result. This can be used as a performance optimization in cases when the node being queried will not change.
This decorator is equivalent to:
@queryAllPermalink to “@queryAll”
query except that it returns all matching nodes, instead of a single node. It's the equivalent of calling
_divs would return both
<div> elements in the template. For TypeScript, the typing of a
@queryAll property is
NodeListOf<HTMLElement>. If you know exactly what kind of nodes you'll retrieve, the typing can be more specific:
The exclamation point (
buttons is TypeScript's non-null assertion operator. It tells the compiler to treat
buttons as always being defined, never
@queryAsyncPermalink to “@queryAsync”
@query, except that instead of returning a node directly, it returns a
Promise that resolves to that node after any pending element render is completed. Code can use this instead of waiting for the
This is useful, for example, if the node returned by
@queryAsync can change as a result of another property change.
Rendering children with slotsPermalink to “Rendering children with slots”
Your component may accept children (like a
<ul> element can have
By default, if an element has a shadow tree, its children don't render at all.
To render children, your template needs to include one or more
<slot> elements, which act as placeholders for child nodes.
Using the slot elementPermalink to “Using the slot element”
To render an element's children, create a
<slot> for them in the element's template. The children aren't moved in the DOM tree, but they're rendered as if they were children of the
<slot>. For example:
Using named slotsPermalink to “Using named slots”
To assign a child to a specific slot, ensure that the child's
slot attribute matches the slot's
Named slots only accept children with a matching
<slot name="one"></slot>only accepts children with the attribute
Children with a
slotattribute will only be rendered in a slot with a matching
<p slot="one">...</p>will only be placed in
Specifying slot fallback contentPermalink to “Specifying slot fallback content”
You can specify fallback content for a slot. The fallback content is shown when no child is assigned to the slot.
Rendering fallback content. If any child nodes are assigned to a slot, its fallback content doesn't render. A default slot with no name accepts any child nodes. It won't render fallback content even if the only assigned nodes are text nodes containing whitespace, for example
<example-element> </example-element>. When using a Lit expression as a child of a custom element, make sure to use a non-rendering value when appropriate so that any slot fallback content is rendered. See removing child content for more information.
Accessing slotted childrenPermalink to “Accessing slotted children”
To access children assigned to slots in your shadow root, you can use the standard
slot.assignedElements methods with the
For example, you can create a getter to access assigned elements for a particular slot:
You can also use the
slotchange event to take action when the assigned nodes change. The following example extracts the text content of all of the slotted children.
For more information, see HTMLSlotElement on MDN.
@queryAssignedElements and @queryAssignedNodes decoratorsPermalink to “@queryAssignedElements and @queryAssignedNodes decorators”
@queryAssignedNodes convert a class property into a getter that returns the result of calling
slot.assignedNodes respectively on a given slot in the component's shadow tree. Use these to query the elements or nodes assigned to a given slot.
Both accept an optional object with the following properties:
|Boolean specifying whether to flatten the assigned nodes by replacing any child |
|Slot name specifying the slot to query. Leave undefined to select the default slot.|
|If specified, only return assigned elements that match this CSS selector.|
Deciding which decorator to use depends on whether you want to query for text nodes assigned to the slot, or only element nodes. This decision is specific to your use case.
The examples above are equivalent to the following code:
Customizing the render rootPermalink to “Customizing the render root”
Each Lit component has a render root—a DOM node that serves as a container for its internal DOM.
By default, LitElement creates an open
shadowRoot and renders inside it, producing the following DOM structure:
There are two ways to customize the render root used by LitElement:
- Implementing the
Setting Permalink to “Setting shadowRootOptions”
The simplest way to customize the render root is to set the
shadowRootOptions static property. The default implementation of
shadowRootOptions as the options argument to
attachShadow when creating the component's shadow root. It can be set to customize any options allowed in the ShadowRootInit dictionary, for example
See Element.attachShadow() on MDN for more information.
Implementing Permalink to “Implementing createRenderRoot”
The default implementation of
createRenderRoot creates an open shadow root and adds to it any styles set in the
static styles class field. For more information on styling see Styles.
To customize a component's render root, implement
createRenderRoot and return the node you want the template to render into.
For example, to render the template into the main DOM tree as your element's children, implement
createRenderRoot and return
Rendering into children. Rendering into children and not shadow DOM is generally not recommended. Your element will not have access to DOM or style scoping, and it will not be able to compose elements into its internal DOM.