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.querySelector
won'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:
- Shadow DOM v1: Self-Contained Web Components on Web Fundamentals.
- Using shadow DOM on MDN.
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 DOM
Permalink 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 this.renderRoot.querySelector()
.
The renderRoot
should always be either a shadow root or an element, which share APIs like .querySelectorAll()
and .children
.
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 decorators
Permalink to “@query, @queryAll, and @queryAsync decorators”The @query
, @queryAll
, and @queryAsync
decorators all provide a convenient way to access nodes in the internal component DOM.
Using decorators. Decorators are a proposed JavaScript feature, so you’ll need to use a compiler like Babel or TypeScript to use decorators. See Using decorators for details.
@query
Permalink 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:
@queryAll
Permalink to “@queryAll”Identical to query
except that it returns all matching nodes, instead of a single node. It's the equivalent of calling querySelectorAll
.
Here, _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 (!
) after buttons
is TypeScript's non-null assertion operator. It tells the compiler to treat buttons
as always being defined, never null
or undefined
.
@queryAsync
Permalink to “@queryAsync”Similar to @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 updateComplete
promise.
This is useful, for example, if the node returned by @queryAsync
can change as a result of another property change.
Rendering children with slots
Permalink to “Rendering children with slots”Your component may accept children (like a <ul>
element can have <li>
children).
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 element
Permalink 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 slots
Permalink to “Using named slots”To assign a child to a specific slot, ensure that the child's slot
attribute matches the slot's name
attribute:
Named slots only accept children with a matching
slot
attribute.For example,
<slot name="one"></slot>
only accepts children with the attributeslot="one"
.Children with a
slot
attribute will only be rendered in a slot with a matchingname
attribute.For example,
<p slot="one">...</p>
will only be placed in<slot name="one"></slot>
.
Specifying slot fallback content
Permalink 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 children
Permalink to “Accessing slotted children”To access children assigned to slots in your shadow root, you can use the standard slot.assignedNodes
or slot.assignedElements
methods with the slotchange
event.
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 decorators
Permalink to “@queryAssignedElements and @queryAssignedNodes decorators”@queryAssignedElements
and @queryAssignedNodes
convert a class property into a getter that returns the result of calling slot.assignedElements
or 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:
Property | Description |
---|---|
flatten | Boolean specifying whether to flatten the assigned nodes by replacing any child <slot> elements with their assigned nodes. |
slot | Slot name specifying the slot to query. Leave undefined to select the default slot. |
selector (queryAssignedElements only) | 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.
Using decorators. Decorators are a proposed JavaScript feature, so you’ll need to use a compiler like Babel or TypeScript to use decorators. See Using decorators for details.
The examples above are equivalent to the following code:
Customizing the render root
Permalink 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:
- Setting
shadowRootOptions
. - Implementing the
createRenderRoot
method.
Setting shadowRootOptions
Permalink to “Setting shadowRootOptions” The simplest way to customize the render root is to set the shadowRootOptions
static property. The default implementation of createRenderRoot
passes 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 mode
and delegatesFocus
.
See Element.attachShadow() on MDN for more information.
Implementing createRenderRoot
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 this
.
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.