Shadow DOM Slots and Composition in JavaScript

Shadow DOM Slots and Composition in JavaScript

Shadow DOM Slots and Composition in JavaScript

The Shadow DOM provides encapsulation, allowing developers to build self-contained custom elements with their own internal structure, styles, and behaviors. Slots and Composition are key concepts used in the Shadow DOM to create flexible and reusable components.

  • Slots allow you to define placeholders in the Shadow DOM where external content can be inserted.
  • Composition involves composing complex elements using multiple parts, allowing developers to pass content into a shadow tree through these slots.

What Are Shadow DOM Slots?

A slot is a placeholder in the Shadow DOM that can receive content from the outside. It allows the insertion of external HTML elements or text into the shadow DOM, enabling the creation of flexible, reusable components where the internal structure can be modified without changing the component itself.

Slots are particularly useful when you want to allow the consumers of your component to inject content while maintaining the encapsulation of your custom element.

Types of Slots:

  1. Default Slot: A single slot that can contain any content if no other slot is provided.
  2. Named Slots: Slots that allow you to specify multiple content insertion points, each identified by a name.

Basic Syntax of Slots in Shadow DOM

Default Slot Example

<template id="card-template"> <style> .card { border: 1px solid #ccc; padding: 16px; border-radius: 8px; } </style> <div class="card"> <slot></slot> <!-- Default slot for content insertion --> </div> </template> <script> class CardElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); const template = document.getElementById('card-template'); this.shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('card-element', CardElement); </script> <card-element> <p>This is content inside the default slot.</p> </card-element>

Explanation:

  • The <slot></slot> element acts as a placeholder in the shadow DOM.
  • The The <p> tag outside the custom element is inserted into the slot when the component is rendered.
  • The content in the slot is rendered inside the shadow DOM of the custom element, while the component remains encapsulated.

Named Slot Example

You can define multiple slots in the Shadow DOM and use the name attribute to distinguish between different content insertion points.

<template id="profile-template"> <style> .profile { border: 1px solid #eee; padding: 16px; width: 200px; } </style> <div class="profile"> <h3><slot name="username"></slot></h3> <!-- Named slot for username --> <p><slot name="bio"></slot></p> <!-- Named slot for bio --> </div> </template> <script> class ProfileCard extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); const template = document.getElementById('profile-template'); this.shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('profile-card', ProfileCard); </script> <profile-card> <span slot="username">John Doe</span> <span slot="bio">Web Developer</span> </profile-card>

Explanation:

  • There are two named slots: one for the username and one for the bio.
  • The span elements with the slot attribute specifies where the content should go in the shadow DOM.
  • The content inserted into the named slots replaces the <slot> elements within the shadow DOM.

Fallback Content in Slots

When using slots, you can also provide fallback content. This content will be displayed if no external content is provided for that slot.

Example with Fallback Content

<template id="profile-template"> <style> .profile { border: 1px solid #eee; padding: 16px; width: 200px; } </style> <div class="profile"> <h3><slot name="username">Default Username</slot></h3> <!-- Fallback content --> <p><slot name="bio">Default Bio</slot></p> <!-- Fallback content --> </div> </template> <script> class ProfileCard extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); const template = document.getElementById('profile-template'); this.shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('profile-card', ProfileCard); </script> <profile-card> <!-- Only the username slot is provided; bio will show fallback content --> <span slot="username">Alice</span> </profile-card>

Explanation:

  • If no content is passed for the username or bio slots, the fallback content ("Default Username" and "Default Bio") will be displayed.

Slot Attributes

You can use several attributes in slots to fine-tune their behavior:

  1. name: Specifies the name of the slot (used for named slots).
  2. part: Allows you to assign a part name to the slot for styling with CSS.

Example:

<template id="my-template"> <div class="card"> <slot name="title"></slot> <!-- Named slot --> <slot name="content"></slot> <!-- Named slot --> </div> </template> <script> class MyComponent extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); const template = document.getElementById('my-template'); this.shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('my-component', MyComponent); </script> <my-component> <span slot="title">Card Title</span> <p slot="content">Card Content</p> </my-component>

Slot Composition and Nested Shadow DOM

You can use slot composition to insert content into a shadow DOM, and combine multiple custom elements that have their own shadow DOM. This allows you to compose complex structures from smaller, reusable components.

Example: Nested Shadow DOM with Slots

<template id="outer-template"> <style> .outer { border: 2px solid #ccc; padding: 20px; } </style> <div class="outer"> <slot name="header"></slot> <!-- Named slot for header --> <div class="content"> <slot name="body"></slot> <!-- Named slot for body --> </div> </div> </template> <template id="inner-template"> <style> .inner { border: 1px solid #999; padding: 10px; } </style> <div class="inner"> <slot></slot> <!-- Default slot for internal content --> </div> </template> <script> class OuterComponent extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); const template = document.getElementById('outer-template'); this.shadowRoot.appendChild(template.content.cloneNode(true)); } } class InnerComponent extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); const template = document.getElementById('inner-template'); this.shadowRoot.appendChild(template.content.cloneNode(true)); } } customElements.define('outer-component', OuterComponent); customElements.define('inner-component', InnerComponent); </script> <!-- Using the components --> <outer-component> <span slot="header">Header Content</span> <inner-component slot="body"> <p>Body Content in Inner Component</p> </inner-component> </outer-component>

Explanation:

  • OuterComponent uses named slots (header, body) to define where content from the outside can be inserted.
  • InnerComponent is nested inside the body slot of the OuterComponent and uses a default slot for internal content.
  • The composition of these components allows flexible and reusable UI patterns, where each part of the UI can be defined in a separate shadow DOM.

Conclusion

  • Slots in the Shadow DOM provide a way to insert external content into shadow trees, enabling flexible and reusable components.
  • You can use named slots for multiple insertion points and default slots for generic content.
  • Fallback content can be provided for slots that don’t receive external content.
  • Composition allows you to build complex components by nesting shadow DOMs, making it easier to create modular and reusable UI elements.
Soeng Souy

Soeng Souy

Website that learns and reads, PHP, Framework Laravel, How to and download Admin template sample source code free.

Post a Comment

CAN FEEDBACK
close