javascript
examples
examples.jsā”javascript
/**
* Shadow DOM - Examples
*
* Comprehensive examples of using Shadow DOM for encapsulation
*/
// ============================================
// EXAMPLE 1: Basic Shadow DOM
// ============================================
class BasicShadow extends HTMLElement {
constructor() {
super();
// Create shadow root with open mode
const shadow = this.attachShadow({ mode: 'open' });
// Add content to shadow root
shadow.innerHTML = `
<style>
/* These styles are scoped to this component */
p {
color: blue;
font-family: Georgia, serif;
padding: 10px;
border-left: 3px solid blue;
}
</style>
<p>This paragraph is styled within Shadow DOM</p>
`;
}
}
customElements.define('basic-shadow', BasicShadow);
/*
Note: External CSS like 'p { color: red; }' will NOT affect
the paragraph inside the shadow DOM
*/
// ============================================
// EXAMPLE 2: Open vs Closed Mode
// ============================================
class OpenShadow extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `<p>Open shadow - accessible</p>`;
}
}
class ClosedShadow extends HTMLElement {
constructor() {
super();
// Store reference privately
this._shadowRoot = this.attachShadow({ mode: 'closed' });
this._shadowRoot.innerHTML = `<p>Closed shadow - not accessible</p>`;
}
// Provide controlled access
updateContent(text) {
this._shadowRoot.querySelector('p').textContent = text;
}
}
customElements.define('open-shadow', OpenShadow);
customElements.define('closed-shadow', ClosedShadow);
// Demo
function demonstrateModes() {
const open = document.createElement('open-shadow');
const closed = document.createElement('closed-shadow');
console.log('Open mode shadowRoot:', open.shadowRoot); // ShadowRoot
console.log('Closed mode shadowRoot:', closed.shadowRoot); // null
}
// ============================================
// EXAMPLE 3: :host Selector Patterns
// ============================================
class HostStyling extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
/* Base host styles */
:host {
display: block;
padding: 20px;
border: 2px solid #e5e7eb;
border-radius: 8px;
font-family: system-ui, sans-serif;
background: white;
transition: all 0.3s ease;
}
/* Host with specific attribute */
:host([variant="primary"]) {
background: #3b82f6;
color: white;
border-color: #2563eb;
}
:host([variant="success"]) {
background: #22c55e;
color: white;
border-color: #16a34a;
}
:host([variant="warning"]) {
background: #f59e0b;
color: white;
border-color: #d97706;
}
:host([variant="danger"]) {
background: #ef4444;
color: white;
border-color: #dc2626;
}
/* Host with class */
:host(.elevated) {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
:host(.rounded) {
border-radius: 20px;
}
/* Host when disabled */
:host([disabled]) {
opacity: 0.5;
pointer-events: none;
}
/* Host context - when inside specific container */
:host-context(.dark-theme) {
background: #1f2937;
color: #f3f4f6;
border-color: #374151;
}
/* Host on hover */
:host(:hover) {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
}
/* Internal styles */
.content {
font-size: 14px;
line-height: 1.6;
}
</style>
<div class="content">
<slot>Default content</slot>
</div>
`;
}
}
customElements.define('host-styling', HostStyling);
/* Usage:
<host-styling variant="primary" class="elevated">
Primary button with elevation
</host-styling>
*/
// ============================================
// EXAMPLE 4: Slot Composition
// ============================================
class CardComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
font-family: system-ui, sans-serif;
}
.card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.card-header {
padding: 16px 20px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
}
.card-body {
padding: 20px;
}
.card-footer {
padding: 16px 20px;
background: #f8fafc;
border-top: 1px solid #e2e8f0;
display: flex;
justify-content: flex-end;
gap: 8px;
}
/* Style slotted content */
::slotted(h2) {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #1e293b;
}
::slotted(p) {
margin: 0;
color: #64748b;
line-height: 1.6;
}
::slotted(button) {
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
}
/* Hide empty slots */
.card-header:not(:has(::slotted(*))) {
display: none;
}
.card-footer:not(:has(::slotted(*))) {
display: none;
}
</style>
<div class="card">
<div class="card-header">
<slot name="header"></slot>
</div>
<div class="card-body">
<slot>No content provided</slot>
</div>
<div class="card-footer">
<slot name="footer"></slot>
</div>
</div>
`;
}
}
customElements.define('card-component', CardComponent);
/* Usage:
<card-component>
<h2 slot="header">Card Title</h2>
<p>This is the main card content.</p>
<button slot="footer">Cancel</button>
<button slot="footer">Save</button>
</card-component>
*/
// ============================================
// EXAMPLE 5: Slot Change Events
// ============================================
class SlotObserver extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
padding: 16px;
background: #f0f9ff;
border-radius: 8px;
}
.info {
font-size: 12px;
color: #64748b;
margin-top: 8px;
}
</style>
<slot></slot>
<div class="info"></div>
`;
}
connectedCallback() {
const slot = this.shadowRoot.querySelector('slot');
const info = this.shadowRoot.querySelector('.info');
// Update info when slot content changes
const updateInfo = () => {
const elements = slot.assignedElements();
const nodes = slot.assignedNodes();
info.innerHTML = `
Assigned elements: ${elements.length}<br>
Assigned nodes: ${nodes.length}<br>
Element types: ${
elements.map((el) => el.tagName).join(', ') || 'none'
}
`;
};
// Listen for slot changes
slot.addEventListener('slotchange', (e) => {
console.log('Slot content changed');
updateInfo();
});
// Initial update
updateInfo();
}
}
customElements.define('slot-observer', SlotObserver);
// ============================================
// EXAMPLE 6: CSS Custom Properties for Theming
// ============================================
class ThemeableButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
/* Default values for custom properties */
--btn-bg: #3b82f6;
--btn-color: white;
--btn-padding: 10px 20px;
--btn-radius: 6px;
--btn-font-size: 14px;
--btn-hover-bg: #2563eb;
--btn-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: inline-block;
}
button {
background: var(--btn-bg);
color: var(--btn-color);
padding: var(--btn-padding);
border-radius: var(--btn-radius);
font-size: var(--btn-font-size);
border: none;
cursor: pointer;
box-shadow: var(--btn-shadow);
transition: all 0.2s ease;
font-family: inherit;
}
button:hover {
background: var(--btn-hover-bg);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
button:active {
transform: translateY(0);
}
/* Variant classes that can be set via attribute */
:host([size="small"]) {
--btn-padding: 6px 12px;
--btn-font-size: 12px;
}
:host([size="large"]) {
--btn-padding: 14px 28px;
--btn-font-size: 16px;
}
</style>
<button>
<slot>Button</slot>
</button>
`;
}
}
customElements.define('themeable-button', ThemeableButton);
/* External theming:
<style>
themeable-button.danger {
--btn-bg: #ef4444;
--btn-hover-bg: #dc2626;
}
themeable-button.success {
--btn-bg: #22c55e;
--btn-hover-bg: #16a34a;
}
</style>
<themeable-button>Default</themeable-button>
<themeable-button class="danger">Delete</themeable-button>
<themeable-button class="success" size="large">Confirm</themeable-button>
*/
// ============================================
// EXAMPLE 7: Event Handling Across Shadow Boundary
// ============================================
class EventBubbling extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
padding: 20px;
background: #f1f5f9;
border-radius: 8px;
}
button {
padding: 10px 20px;
margin: 5px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.standard { background: #e2e8f0; }
.composed { background: #bfdbfe; }
</style>
<p>Click the buttons and check the console:</p>
<button class="standard">Standard Event</button>
<button class="composed">Composed Event</button>
`;
}
connectedCallback() {
const standardBtn = this.shadowRoot.querySelector('.standard');
const composedBtn = this.shadowRoot.querySelector('.composed');
// Standard custom event - stops at shadow boundary
standardBtn.addEventListener('click', () => {
this.dispatchEvent(
new CustomEvent('standard-click', {
bubbles: true,
composed: false, // Won't cross shadow boundary
detail: { message: 'Standard event' },
})
);
});
// Composed custom event - crosses shadow boundary
composedBtn.addEventListener('click', () => {
this.dispatchEvent(
new CustomEvent('composed-click', {
bubbles: true,
composed: true, // Crosses shadow boundary
detail: { message: 'Composed event' },
})
);
});
// Internal click handler to show retargeting
this.shadowRoot.addEventListener('click', (e) => {
console.log('Inside shadow - event.target:', e.target);
});
}
}
customElements.define('event-bubbling', EventBubbling);
/*
document.addEventListener('click', (e) => {
// e.target will be the host element, not internal button
console.log('Document - event.target:', e.target);
console.log('Full path:', e.composedPath());
});
*/
// ============================================
// EXAMPLE 8: Focus Delegation
// ============================================
class FocusDelegate extends HTMLElement {
constructor() {
super();
// Enable focus delegation
this.attachShadow({
mode: 'open',
delegatesFocus: true,
});
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
padding: 16px;
border: 2px solid #e5e7eb;
border-radius: 8px;
transition: border-color 0.2s;
}
:host(:focus-within) {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
input {
width: 100%;
padding: 10px;
border: 1px solid #d1d5db;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
input:focus {
outline: none;
border-color: #3b82f6;
}
</style>
<label>
<slot name="label">Enter value:</slot>
</label>
<input type="text" placeholder="Focus delegates here">
`;
}
}
customElements.define('focus-delegate', FocusDelegate);
/*
When you click anywhere on the component or call .focus(),
focus will automatically go to the first focusable element (input)
*/
// ============================================
// EXAMPLE 9: Adoptable Stylesheets
// ============================================
class AdoptedStyles extends HTMLElement {
static styles = null;
static {
// Create shared stylesheet (only once)
if ('adoptedStyleSheets' in Document.prototype) {
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
:host {
display: block;
padding: 16px;
}
.container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
}
h3 {
margin: 0 0 8px 0;
}
p {
margin: 0;
opacity: 0.9;
}
`);
AdoptedStyles.styles = sheet;
}
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
// Use adopted stylesheets if supported
if (AdoptedStyles.styles) {
this.shadowRoot.adoptedStyleSheets = [AdoptedStyles.styles];
} else {
// Fallback for older browsers
this.shadowRoot.innerHTML = `<style>/* fallback styles */</style>`;
}
this.shadowRoot.innerHTML += `
<div class="container">
<h3><slot name="title">Title</slot></h3>
<p><slot>Content</slot></p>
</div>
`;
}
}
customElements.define('adopted-styles', AdoptedStyles);
// ============================================
// EXAMPLE 10: Part and Exportparts
// ============================================
class PartExample extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
padding: 16px;
}
.button {
padding: 10px 20px;
border: none;
border-radius: 4px;
background: #e5e7eb;
cursor: pointer;
}
.icon {
margin-right: 8px;
}
.label {
font-weight: 500;
}
</style>
<button class="button" part="button">
<span class="icon" part="icon">ā</span>
<span class="label" part="label">
<slot>Click me</slot>
</span>
</button>
`;
}
}
customElements.define('part-example', PartExample);
/* External styling with ::part:
<style>
part-example::part(button) {
background: #3b82f6;
color: white;
}
part-example::part(button):hover {
background: #2563eb;
}
part-example::part(icon) {
font-size: 1.2em;
}
</style>
*/
// ============================================
// EXAMPLE 11: Complex Shadow DOM Layout
// ============================================
class DashboardPanel extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
--panel-bg: #ffffff;
--panel-border: #e5e7eb;
--header-bg: #f8fafc;
--header-color: #1e293b;
--content-padding: 20px;
display: block;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: 12px;
overflow: hidden;
font-family: system-ui, sans-serif;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px var(--content-padding);
background: var(--header-bg);
border-bottom: 1px solid var(--panel-border);
}
.title {
font-size: 16px;
font-weight: 600;
color: var(--header-color);
margin: 0;
}
.actions {
display: flex;
gap: 8px;
}
.content {
padding: var(--content-padding);
min-height: 100px;
}
.footer {
padding: 12px var(--content-padding);
background: var(--header-bg);
border-top: 1px solid var(--panel-border);
font-size: 12px;
color: #64748b;
}
/* Responsive */
@media (max-width: 600px) {
:host {
--content-padding: 16px;
}
.header {
flex-direction: column;
gap: 12px;
}
}
/* Hide empty footer */
.footer:empty {
display: none;
}
</style>
<div class="header">
<h2 class="title">
<slot name="title">Panel Title</slot>
</h2>
<div class="actions">
<slot name="actions"></slot>
</div>
</div>
<div class="content">
<slot></slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
`;
}
}
customElements.define('dashboard-panel', DashboardPanel);
// ============================================
// USAGE DEMONSTRATION
// ============================================
function demonstrateShadowDOM() {
console.log('=== Shadow DOM Demonstration ===\n');
// Check support
console.log('Shadow DOM supported:', 'attachShadow' in Element.prototype);
console.log(
'Adopted StyleSheets supported:',
'adoptedStyleSheets' in Document.prototype
);
// Show registered components
console.log('\nComponents with Shadow DOM:');
const components = [
'basic-shadow',
'host-styling',
'card-component',
'themeable-button',
'dashboard-panel',
];
components.forEach((name) => {
console.log(` <${name}>`);
});
}
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
BasicShadow,
OpenShadow,
ClosedShadow,
HostStyling,
CardComponent,
SlotObserver,
ThemeableButton,
EventBubbling,
FocusDelegate,
AdoptedStyles,
PartExample,
DashboardPanel,
demonstrateShadowDOM,
};
}