Composing Components — Complete Guide
In this tutorial, you will learn about Composing Components. We cover key concepts, practical examples, and best practices to help you master this topic.
Composing Web Components involves nesting custom elements, passing data via attributes and slots, and coordinating communication between parent and child components.
What You'll Learn
- How to nest Web Components inside each other
- How parent components access child components
- How to pass data between nested components
- How to handle component hierarchies
Why It Matters
Real applications are composed of many components working together. A form contains inputs, buttons, and validation messages. A dashboard contains cards, charts, and filters.
flowchart LR A[App Shell] --> B[Sidebar] A --> C[Main Content] B --> D[Nav Item] B --> E[Nav Item] C --> F[Card] C --> G[Card] F --> H[Button] G --> H
Nesting Components
class AppShell extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host { display: grid; grid-template-columns: 250px 1fr; min-height: 100vh; }
nav { background: #2c3e50; color: white; padding: 1rem; }
main { padding: 1rem; }
</style>
<nav><slot name="sidebar"></slot></nav>
<main><slot></slot></main>
`;
}
}
customElements.define('app-shell', AppShell);
class DataCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.card { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: 1rem 0; }
h3 { margin: 0 0 0.5rem; }
</style>
<div class="card">
<h3><slot name="title"></slot></h3>
<slot></slot>
</div>
`;
}
}
customElements.define('data-card', DataCard);
Parent-Child Communication
class AccordionGroup extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = '<slot></slot>';
this.addEventListener('accordion-toggle', this._handleToggle);
}
_handleToggle(event) {
const target = event.target;
const items = this.querySelectorAll('accordion-item');
items.forEach(item => {
if (item !== target) {
item.removeAttribute('open');
}
});
}
}
customElements.define('accordion-group', AccordionGroup);
class AccordionItem extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.header { padding: 12px; background: #f5f5f5; cursor: pointer; border: 1px solid #ddd; }
.content { padding: 12px; display: none; border: 1px solid #ddd; border-top: none; }
:host([open]) .content { display: block; }
</style>
<div class="header"><slot name="header">Section</slot></div>
<div class="content"><slot></slot></div>
`;
this.shadowRoot.querySelector('.header')
.addEventListener('click', () => this.toggle());
}
toggle() {
if (this.hasAttribute('open')) {
this.removeAttribute('open');
} else {
this.dispatchEvent(new CustomEvent('accordion-toggle', { bubbles: true }));
this.setAttribute('open', '');
}
}
}
customElements.define('accordion-item', AccordionItem);
Common Mistakes
- Hard-coding child selectors instead of using slots
- Not dispatching events with bubbles:true for parent communication
- Tight coupling between parent and child components
Practice Questions
- How do components communicate upward? Dispatch custom events with bubbles:true.
- How do you restrict slot content to specific component types? Use CSS ::slotted with selectors or validate in connectedCallback.
Mini Project
Build a tab component with tab-group and tab-panel child components. Tab-group manages which tab is active. Tab-panel shows/hides based on the active state. Use custom events for communication.
What's Next
Lesson 11: CSS Custom Properties in Components
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro