Learn about slots, composition, and accessibility
This example demonstrates advanced Web Component patterns:
Click the buttons below to open different modal configurations:
This is a simple modal using the default slot for content.
Try pressing ESC to close, or click outside the modal.
Are you sure you want to proceed with this action?
This action cannot be undone.
class ModalDialog extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
// Create modal structure with slots
this.shadowRoot.innerHTML = `
<style>
:host {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
}
:host([open]) {
display: flex;
align-items: center;
justify-content: center;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
animation: fadeIn 0.2s ease-out;
}
.modal {
position: relative;
background: white;
border-radius: 0;
padding: 0;
max-width: 600px;
width: 90%;
max-height: 90vh;
overflow: auto;
box-shadow: 0 20px 25px rgba(0, 0, 0, 0.15);
animation: slideIn 0.3s ease-out;
}
.modal-header {
padding: 24px;
border-bottom: 1px solid #e8e3db;
}
.modal-body {
padding: 24px;
}
.modal-footer {
padding: 20px 24px;
border-top: 1px solid #e8e3db;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.close-btn {
position: absolute;
top: 20px;
right: 20px;
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #6b6b6b;
line-height: 1;
padding: 0;
width: 32px;
height: 32px;
}
.close-btn:hover {
color: #1a1a1a;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
<div class="overlay" part="overlay"></div>
<div class="modal" role="dialog" aria-modal="true" part="modal">
<button class="close-btn" aria-label="Close modal">×</button>
<div class="modal-header">
<slot name="header"><h2>Modal</h2></slot>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<slot name="footer"></slot>
</div>
</div>
`;
this.setupEventListeners();
}
setupEventListeners() {
// Close button
const closeBtn = this.shadowRoot.querySelector('.close-btn');
closeBtn.addEventListener('click', () => this.close());
// Click overlay to close
const overlay = this.shadowRoot.querySelector('.overlay');
overlay.addEventListener('click', () => this.close());
// Prevent modal content clicks from closing
const modal = this.shadowRoot.querySelector('.modal');
modal.addEventListener('click', (e) => e.stopPropagation());
// ESC key to close
this.handleKeyDown = (e) => {
if (e.key === 'Escape' && this.hasAttribute('open')) {
this.close();
}
};
}
open() {
this.setAttribute('open', '');
document.addEventListener('keydown', this.handleKeyDown);
// Focus management
this.previousFocus = document.activeElement;
const modal = this.shadowRoot.querySelector('.modal');
modal.focus();
// Dispatch custom event
this.dispatchEvent(new CustomEvent('modalopen', {
bubbles: true,
composed: true
}));
}
close() {
this.removeAttribute('open');
document.removeEventListener('keydown', this.handleKeyDown);
// Restore focus
if (this.previousFocus) {
this.previousFocus.focus();
}
// Dispatch custom event
this.dispatchEvent(new CustomEvent('modalclose', {
bubbles: true,
composed: true
}));
}
disconnectedCallback() {
// Cleanup when element is removed
document.removeEventListener('keydown', this.handleKeyDown);
}
}
customElements.define('modal-dialog', ModalDialog);
<!-- Simple Modal -->
<modal-dialog id="myModal">
<h2 slot="header">Modal Title</h2>
<p>This goes in the default slot (body).</p>
<div slot="footer">
<button>OK</button>
</div>
</modal-dialog>
<!-- Open the modal -->
<script>
document.querySelector('#myModal').open();
</script>
Slots are placeholders that allow users to insert their own content into a component. There are two types:
<slot></slot> - captures content without a slot attribute<slot name="header"></slot> - captures content with matching slot attributerole="dialog" and aria-modal="true" for screen readersaria-label on close button for screen reader usersSlots enable composition - you can nest components and custom content:
connectedCallback(): Setup when added to DOMdisconnectedCallback(): Cleanup when removed from DOMOpen the browser console and try:
// Get a modal
const modal = document.querySelector('#simpleModal');
// Open it
modal.open();
// Listen to events
modal.addEventListener('modalopen', () => console.log('Modal opened!'));
modal.addEventListener('modalclose', () => console.log('Modal closed!'));