Web Components Basics
This lesson explains Web Components Basics with clear examples, use cases, and best practices so you can build reusable Web Components confidently.
Web Components Basics Overview
Web Components are a set of browser-native technologies
that allow developers to create reusable, encapsulated, framework-friendly
UI components using standard HTML, CSS, and JavaScript.
A Web Component can define its own custom HTML tag, render internal UI,
isolate styles with Shadow DOM, expose slots for content, react to
attributes, dispatch custom events, and work in plain JavaScript, React,
Angular, Vue, Svelte, Astro, and server-rendered pages.
Web Components Basics List
- 1. Web Components are browser-native components.
They do not require a frontend framework. They are built using standard
browser APIs.
<my-card>
Hello Web Components
</my-card>
- 2. Web Components use custom elements.
A custom element is a reusable HTML tag created with JavaScript.
Custom element names must include a hyphen.
<user-profile></user-profile>
<app-header></app-header>
<product-card></product-card>
- 3. Define a custom element with a class.
Custom elements extend HTMLElement.
class MyGreeting extends HTMLElement {
connectedCallback() {
this.textContent =
"Hello from a Web Component!";
}
}
customElements.define(
"my-greeting",
MyGreeting
);
- 4. Register custom elements with
customElements.define().
The browser connects a custom tag name to a JavaScript class.
customElements.define(
"my-button",
MyButton
);
- 5. Custom element names must contain a hyphen.
This prevents conflicts with built-in HTML tags.
<my-button></my-button>
<button-card></button-card>
- 6. Use
connectedCallback() for setup.
connectedCallback() runs when the element is added to the
DOM. It is commonly used for rendering, event listeners, and setup.
connectedCallback() {
this.innerHTML =
`
<p>
Component is connected.
</p>
`;
}
- 7. Use
disconnectedCallback() for cleanup.
disconnectedCallback() runs when the element is removed
from the DOM. Use it to clean up timers, observers, and global listeners.
connectedCallback() {
this.timer =
setInterval(() => {
console.log("Running");
}, 1000);
}
disconnectedCallback() {
clearInterval(this.timer);
}
- 8. Use Shadow DOM for encapsulation.
Shadow DOM keeps component markup and styles separate from the main
page.
class MyCard extends HTMLElement {
constructor() {
super();
this.attachShadow(
{ mode: "open" }
);
}
connectedCallback() {
this.shadowRoot.innerHTML =
`
<style>
article {
border: 1px solid #dbe5f1;
padding: 1rem;
}
</style>
<article>
<slot></slot>
</article>
`;
}
}
- 9. Use slots for flexible content.
Slots allow users to pass content into a Web Component.
<my-card>
<h2>Profile</h2>
<p>User information goes here.</p>
</my-card>
<article>
<slot></slot>
</article>
- 10. Use named slots for specific areas.
Named slots help organize content such as title, body, footer, and
actions.
<my-panel>
<h2 slot="title">
Settings
</h2>
<p>
Update your preferences.
</p>
<button slot="actions">
Save
</button>
</my-panel>
<header>
<slot name="title"></slot>
</header>
<section>
<slot></slot>
</section>
<footer>
<slot name="actions"></slot>
</footer>
- 11. Use attributes for simple configuration.
Attributes allow users to configure Web Components directly in HTML.
<my-alert
type="success"
message="Saved successfully"
open>
</my-alert>
- 12. Read attributes inside a component.
Use getAttribute() to read string values from the host
element.
get type() {
return this.getAttribute("type") || "info";
}
get message() {
return this.getAttribute("message") || "";
}
- 13. Use properties for complex data.
Properties are better for arrays, objects, callbacks, maps, services,
and non-string values.
const table =
document.querySelector("my-table");
table.rows =
[
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
- 14. Observe attributes that affect rendering.
Use observedAttributes to tell the browser which attributes
should trigger updates.
static get observedAttributes() {
return [
"type",
"message",
"open"
];
}
- 15. React to changes with
attributeChangedCallback().
This lifecycle method runs when an observed attribute changes.
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) {
return;
}
this.render();
}
- 16. Dispatch custom events.
Web Components communicate user actions through custom events.
this.dispatchEvent(
new CustomEvent(
"save-click",
{
detail: {
id: 101
},
bubbles: true,
composed: true
}
)
);
- 17. Listen to custom events outside the component.
Parent pages or frameworks can listen for component events.
document
.querySelector("my-editor")
.addEventListener("save-click", (event) => {
console.log(event.detail.id);
});
- 18. Use
bubbles and composed for Shadow DOM events.
bubbles lets the event travel up the DOM. composed
allows the event to cross the Shadow DOM boundary.
new CustomEvent(
"item-select",
{
detail: {
value: "dashboard"
},
bubbles: true,
composed: true
}
);
- 19. Style the host with
:host.
:host targets the custom element itself from inside Shadow
DOM styles.
:host {
display: block;
}
:host([hidden]) {
display: none;
}
- 20. Style slotted content with
::slotted().
::slotted() styles light DOM content assigned to a slot,
but only targets the slotted element itself.
::slotted(h2) {
margin-top: 0;
}
- 21. Expose styling hooks with CSS parts.
Use part inside a component and ::part() from
outside to expose safe styling hooks.
<my-card></my-card>
<style>
my-card::part(title) {
font-weight: 700;
}
</style>
- 22. Use CSS custom properties for theming.
CSS variables can pass theme values from the page into Shadow DOM.
:host {
color: var(--card-text, #111827);
background: var(--card-bg, #ffffff);
}
- 23. Use adopted stylesheets for reusable styles.
Constructable stylesheets can be shared across multiple Web Components.
const sheet =
new CSSStyleSheet();
sheet.replaceSync(
`
:host {
display: block;
}
`
);
this.shadowRoot.adoptedStyleSheets =
[sheet];
- 24. Use templates for reusable markup.
The <template> element stores reusable HTML that can
be cloned into components.
<template id="card-template">
<style>
article {
border: 1px solid #dbe5f1;
padding: 1rem;
}
</style>
<article>
<slot></slot>
</article>
</template>
- 25. Clone templates into Shadow DOM.
Template cloning avoids rebuilding large markup strings repeatedly.
const template =
document.querySelector("#card-template");
this.shadowRoot.appendChild(
template.content.cloneNode(true)
);
- 26. Make components accessible.
Use semantic HTML, labels, ARIA states, keyboard support, focus styles,
and predictable behavior.
<button
type="button"
aria-expanded="false">
Menu
</button>
- 27. Prefer native elements inside components.
Native elements already provide accessibility and keyboard behavior.
<button type="button">
Save
</button>
<input type="email" />
- 28. Clean up event listeners and observers.
Global listeners, timers, observers, and subscriptions should be cleaned
up in disconnectedCallback().
connectedCallback() {
window.addEventListener(
"resize",
this.handleResize
);
}
disconnectedCallback() {
window.removeEventListener(
"resize",
this.handleResize
);
}
- 29. Web Components work with frameworks.
Since Web Components are browser-native elements, they can be used in
many frameworks and static sites.
<product-card
product-id="101"
title="Laptop | PHPKINGDOM">
</product-card>
- 30. Web Components are ideal for design systems.
They are useful for reusable UI libraries, cross-framework components,
widgets, dashboards, documentation sites, and enterprise design systems.
Complete Basic Web Component Example
This example shows a reusable card component with Shadow DOM, slots,
attributes, styling, and a custom event.
class MyCard extends HTMLElement {
static get observedAttributes() {
return [
"variant"
];
}
constructor() {
super();
this.attachShadow(
{ mode: "open" }
);
this.shadowRoot.innerHTML =
`
<style>
:host {
display: block;
font-family: system-ui, sans-serif;
}
article {
border: 1px solid #dbe5f1;
border-radius: 0.75rem;
padding: 1rem;
background: var(--card-bg, #ffffff);
}
:host([variant="highlight"]) article {
border-width: 2px;
}
::slotted([slot="title"]) {
margin-top: 0;
}
</style>
<article part="card">
<header>
<slot name="title"></slot>
</header>
<section>
<slot></slot>
</section>
<footer>
<button type="button">
Select
</button>
</footer>
</article>
`;
}
connectedCallback() {
this.shadowRoot
.querySelector("button")
.addEventListener("click", () => {
this.dispatchEvent(
new CustomEvent(
"card-select",
{
detail: {
variant: this.variant
},
bubbles: true,
composed: true
}
)
);
});
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) {
return;
}
console.log(
name + " changed to " + newValue
);
}
get variant() {
return this.getAttribute("variant") || "default";
}
set variant(value) {
this.setAttribute(
"variant",
value
);
}
}
customElements.define(
"my-card",
MyCard
);
<my-card variant="highlight">
<h2 slot="title">
Web Components
</h2>
<p>
Build reusable browser-native UI.
</p>
</my-card>
document
.querySelector("my-card")
.addEventListener("card-select", (event) => {
console.log(event.detail.variant);
});
Core Web Components APIs Table
| API | Purpose | Example |
| Custom Elements | Create custom HTML tags. | customElements.define() |
| Shadow DOM | Encapsulate markup and styles. | attachShadow() |
| Slots | Pass content into components. | <slot> |
| Templates | Store reusable markup. | <template> |
| Attributes | Configure components from HTML. | variant="primary" |
| Custom Events | Communicate actions to parent code. | new CustomEvent() |
Common Web Components Use Cases
-
Cross-framework design systems.
-
Reusable UI widgets.
-
Enterprise component libraries.
-
Documentation website components.
-
Static site and CMS widgets.
-
Dashboards and admin panels.
-
Micro frontend shared UI.
-
Framework-independent components.
-
Embeddable third-party widgets.
-
Long-lived reusable UI elements.
Common Web Components Basics Mistakes
-
Forgetting the hyphen in custom element names.
-
Rendering repeatedly in
connectedCallback() without guards.
-
Not cleaning up global event listeners.
-
Using attributes for complex objects instead of properties.
-
Not using
composed: true for events crossing Shadow DOM.
-
Forgetting accessibility and keyboard support.
-
Overusing Shadow DOM when simple light DOM is enough.
-
Expecting page CSS to automatically style Shadow DOM internals.
-
Not documenting attributes, properties, events, and slots.
-
Creating components that look native but do not behave accessibly.
Web Components Basics Best Practices
-
Use clear, kebab-case custom element names.
-
Keep components focused on one responsibility.
-
Use Shadow DOM when style or DOM encapsulation is needed.
-
Use slots for flexible content composition.
-
Use attributes for simple configuration.
-
Use properties for complex data.
-
Dispatch custom events for user actions.
-
Clean up timers, observers, and listeners.
-
Build accessibility into every component from the start.
-
Document public APIs: attributes, properties, slots, parts, and events.
Pro Tip
Web Components are strongest when you design them like public APIs:
simple attributes, powerful properties, meaningful slots, stable events,
accessible behavior, and clear documentation.