π Web Components: What they are, What they're for, and How they can simplify your frontend projects
![]() |
In modern frontend development, the need to create modular, reusable and interoperable interfaces is increasingly strong. In this context, Web Components are proposed as a standard, powerful and framework-agnostic solution to address issues of encapsulation, duplication and maintenance of UI code.
In this article we will see:
- What are Web Components and how do they work
- Why are they useful in real scenarios
- A concrete use case adopted by GitHub
- A complete practical example with code and architectural benefits
π What are Web Components?
Web Components are a browser-native technology that allows you to create custom, encapsulated and reusable HTML components, without depending on external frameworks.
They are based on four fundamental specifications:
- Custom Elements – Definition of new HTML tags via JavaScript (
customElements.define()). - Shadow DOM – Encapsulation of style and markup to avoid conflicts with the rest of the page.
- HTML Templates – Reusable HTML templates that do not render until used.
- ES Modules – JavaScript module support for organizing and importing code.
Basic example:
class MyButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = `
<style>
button { background: teal; color: white; padding: 10px; border: none; border-radius: 4px; }
</style>
<button><slot></slot></button>
`;
}
}
customElements.define('my-button', MyButton);
Usage:
<my-button>Click here</my-button>
π― Why use them: advantages and problems solved
| Problem | Solution offered by Web Components |
|---|---|
| Style and JavaScript conflicts | Shadow DOM for complete isolation |
| Code duplication in multiple projects | Components reusable everywhere |
| Framework lock-in | Native and agnostic web standards |
| Hard integration in microfrontend architectures | Interoperable components |
Web Components are ideal for:
- Cross-framework design systems
- Legacy applications
- Distributed microfrontends and widgets
- Shared development between different teams
π’ Real case: GitHub and Web Components
GitHub has adopted Web Components to improve the modularity and scalability of the UI. Real-world examples include:
<relative-time>to show relative dates<markdown-toolbar>for the text editor<details-dialog>for modal windows
Motivation:
- Modular code management
Apple, Adobe, and Salesforce also use Web Components to create scalable design systems and components cross-app.
π§ͺ Practical Example: Reusable Feedback Widget
π― Requirement
In an organization with multiple apps (React, Angular, Vanilla JS), you need a uniform component to collect user feedback:
- Compact and consistent interface
- Asynchronous dispatch to an endpoint
- No style conflicts
- Easy integration into all environments
π ️ Implementation with Web Component
File structure:
feedback-widget/
├── feedback-widget.js
└── index.html
Code feedback-widget.js:
class FeedbackWidget extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
* { font-family: sans-serif; }
.widget { border: 1px solid #ccc; padding: 12px; border-radius: 8px; background: #f9f9f9; width: 250px; }
textarea, select, button { width: 100%; margin-top: 8px; }
.success { color: green; display: none; margin-top: 8px; }
</style>
<div class="widget">
<label for="rating">Rating:</label>
<select id="rating">
<option value="">--</option>
<option value="1">1 - Very bad</option>
<option value="5">5 - Excellent</option>
</select>
<textarea id="comment" placeholder="Write a comment..."></textarea>
<button id="submit">Send Feedback</button>
<div class="success">Thanks for the feedback!</div>
</div>
`;
}
connectedCallback() {
const rating = this.shadowRoot.getElementById('rating');
const comment = this.shadowRoot.getElementById('comment');
const button = this.shadowRoot.getElementById('submit');
const success = this.shadowRoot.querySelector('.success');
button.addEventListener('click', async () => {
const payload = {
rating: rating.value,
comment: comment.value,
};
await fetch('https://example.com/api/feedback', {
method: 'POST',
body: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json' },
});
success.style.display = 'block';
button.disabled = true;
});
}
}
customElements.define('feedback-widget', FeedbackWidget);
HTML demo index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module" src="./feedback-widget.js"></script>
</head>
<body>
<h2>Portal Feedback</h2>
<feedback-widget></feedback-widget>
</body>
</html>
✅ Benefits achieved
| Objective | Result with Web Component |
|---|---|
| Standard UI in all apps | ✅ Centralized style and structure |
| Easy integration | ✅ Native HTML tag |
| Logic/style encapsulation | ✅ Shadow DOM |
| No framework dependencies | ✅ Works everywhere |
| Maintenance and versioning | ✅ Distributable via CDN/NPM |
π Possible extensions
-
Dynamic theme: <feedback-widget theme="dark">
-
Custom events: this.dispatchEvent(new CustomEvent('submitted'))
-
Configurability via attributes (endpoint, lang)
-
Test: unitary with Jest, e2e with Playwright
-
Deployment: via CDN (Skypack, jsDelivr) or npm
Dynamic theme: <feedback-widget theme="dark">
Custom events: this.dispatchEvent(new CustomEvent('submitted'))
Configurability via attributes (endpoint, lang)
Test: unitary with Jest, e2e with Playwright
Deployment: via CDN (Skypack, jsDelivr) or npm
π NOTE: What is connectedCallback()?
It is a lifecycle method that is automatically called by the browser when a Web Component is inserted into the DOM (document HTML).
π What is it for?
It is used to execute initialization code, such as:
- add event listeners,
- fetch data,
- update the interface,
- initialize logic that requires the component to actually be "active" on the page.
π Basic syntax
class MyElement extends HTMLElement {
connectedCallback() {
console.log('The component has been added to the DOM!');
}
}
customElements.define('my-element', MyElement);
π When is it called?
- ✅ As soon as the component is inserted into the DOM, even if dynamically
- ❌ It is not called until the element is created in JS but not yet added to the DOM
π§ Practical example
class HelloWorld extends HTMLElement {
connectedCallback() {
this.innerHTML = `<p>Hello from the Web Component!</p>`;
}
}
customElements.define('hello-world', HelloWorld);
In the DOM:
<hello-world></hello-world>
π When the browser "sees" <hello-world>, it calls connectedCallback() and replaces the content with the defined one.
π§Ή Note: there are also other useful methods
| Method | When called |
|---|---|
connectedCallback() |
When the element enters the DOM |
disconnectedCallback() |
When removed from the DOM |
attributeChangedCallback() |
When observed attributes change |
adoptedCallback() |
When moved between documents (e.g. iframe) |
π§© Conclusion
Web Components are a robust, standard and interoperable alternative for building modern UIs. They offer:
- Perfect isolation of style and logic
- Reuse between frameworks and apps
- Easy integration into microfrontends or legacy portals
- Centralized maintenance and testing
If your team works in mixed environments or on enterprise projects, Web Components can make the difference between a fragile design system and a scalable, robust and modern one.
Follow me #techelopment
Official site: www.techelopment.it
facebook: Techelopment
instagram: @techelopment
X: techelopment
Bluesky: @techelopment
telegram: @techelopment_channel
whatsapp: Techelopment
youtube: @techelopment
