Web Components Introduction
This lesson introduces Web Components as a set of browser-native standards for building reusable, encapsulated custom HTML elements that work across frameworks and plain HTML pages.
What Are Web Components?
Web Components are a collection of web platform APIs that let you
create new HTML tags with their own structure, styling, and
behavior. Instead of treating every UI piece as framework-specific
code, you define a component once and use it like any other HTML
element.
The term Web Components usually refers to three
complementary standards that work together: Custom Elements, Shadow
DOM, and HTML Templates. Modern libraries such as Lit build on top
of these standards rather than replacing them.
| Standard | Purpose |
| Custom Elements | Define new HTML tags and lifecycle behavior |
| Shadow DOM | Encapsulate internal markup and styles |
| HTML Templates | Store reusable markup with <template> |
| Slots | Project external content into a component |
| Custom Events | Communicate from child components to parents |
Your First Web Component
A Web Component starts as a JavaScript class that extends
HTMLElement. You register it with
customElements.define(), then use it in HTML like any
native tag.
class GreetingCard extends HTMLElement {
connectedCallback() {
const name = this.getAttribute("name") || "Guest";
this.innerHTML = `
<article class="card">
<h2>Welcome, ${name}!</h2>
<p>This is a browser-native custom element.</p>
</article>
`;
}
}
customElements.define("greeting-card", GreetingCard);
<greeting-card name="Alex"></greeting-card>
After registration, <greeting-card> behaves like
a first-class HTML element. Frameworks can render it, and plain
pages can include it with a script tag.
How Web Components Work
A typical Web Component combines several building blocks. The class
defines behavior, Shadow DOM isolates internal UI, templates keep
markup reusable, and attributes or properties form the public API.
class ProfileBadge extends HTMLElement {
constructor() {
super();
const template = document.getElementById("profile-badge-template");
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
static get observedAttributes() {
return ["status"];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "status" && oldValue !== newValue) {
this.shadowRoot.querySelector(".status").textContent = newValue;
}
}
}
This pattern separates the public tag from private implementation
details. Consumers interact with attributes and events; the
component manages its own DOM and styles internally.
The Three Core Standards
| Standard | What It Solves | Example |
| Custom Elements | Reusable tags with lifecycle hooks | <date-picker> |
| Shadow DOM | Scoped CSS and hidden internals | attachShadow() |
| HTML Templates | Inactive markup for cloning | <template> |
You do not have to use all three on day one. Many teams start with
Custom Elements alone, then add Shadow DOM when styling isolation
becomes important.
Web Components vs Framework Components
| Topic | Web Components | Framework Components |
| Runtime | Browser-native APIs | Framework runtime required |
| Portability | Works across frameworks | Tied to one ecosystem |
| Encapsulation | Shadow DOM built in | Depends on framework patterns |
| Learning curve | Lower-level browser APIs | Higher-level abstractions |
| Best fit | Shared UI, design systems | App-specific views and state |
Web Components are not a replacement for React, Vue, or Angular.
They complement them. Many production apps use framework components
for application screens and Web Components for shared primitives
such as buttons, modals, and data tables.
Shadow DOM at a Glance
class StyledButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
button {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
}
</style>
<button><slot></slot></button>
`;
}
}
customElements.define("styled-button", StyledButton);
<styled-button>Save changes</styled-button>
Shadow DOM keeps internal styles from leaking out and external
styles from breaking the component. Slots let users pass content
into predefined places inside the shadow tree.
Where Web Components Are Used
- Design systems shared across multiple apps and teams.
- Embeddable widgets for third-party sites.
- Micro frontends with framework-agnostic UI pieces.
- CMS and static sites that need interactive components.
- Browser extensions and progressive web apps.
- Long-lived products that outlive a single framework choice.
Browser Support
Custom Elements, Shadow DOM, and HTML Templates are supported in
all modern evergreen browsers. For older browsers, polyfills such
as @webcomponents/webcomponentsjs can fill gaps, though
most new projects target current browser versions.
| Feature | Support |
| Custom Elements | Chrome, Firefox, Safari, Edge |
| Shadow DOM | Chrome, Firefox, Safari, Edge |
| Declarative Shadow DOM | Growing SSR support in modern browsers |
| Lit and other libraries | Built on top of native APIs |
What You Will Learn in This Tutorial
This Web Components tutorial series walks from fundamentals to
production patterns. Here is the learning path ahead:
- Basics — custom elements, templates, and slots.
- Shadow DOM — encapsulation, styling, and events.
- Lifecycle — connect, disconnect, and attribute callbacks.
- APIs — properties, observed attributes, and custom events.
- Lit — a popular library for building Web Components faster.
- Production — testing, accessibility, performance, and SSR.
Building with Lit (Preview)
Many teams use Lit
to reduce boilerplate. Lit still produces real Web Components under
the hood.
import { LitElement, html, css } from "lit";
class CounterBadge extends LitElement {
static properties = {
count: { type: Number }
};
static styles = css`
span {
font-weight: bold;
}
`;
render() {
return html`<span>${this.count}</span>`;
}
}
customElements.define("counter-badge", CounterBadge);
You will learn Lit later in this series. Starting with native APIs
first helps you understand what Lit abstracts away.
When to Use Web Components
- You need UI shared across multiple frameworks or teams.
- You want encapsulation without a heavy runtime dependency.
- You are building a design system or component library.
- You need embeddable widgets for external sites.
- You want standards-based components with long-term portability.
- You are combining micro frontends with different tech stacks.
When to Choose Something Else
- Your entire app lives in one framework with no sharing needs.
- You need advanced state management built into the component layer.
- Your team prefers framework-specific developer tooling only.
- You are building a small prototype where speed matters more than reuse.
In those cases, framework components may be simpler. Web Components
shine when reuse, encapsulation, and interoperability matter.
Getting Started Best Practices
- Start with a small custom element before adding Shadow DOM.
- Use hyphenated tag names such as
app-header. - Keep the public API small: a few attributes and events.
- Think about accessibility from the first component you build.
- Document attributes, properties, slots, and emitted events.
- Test components in plain HTML before integrating with a framework.
- Prefer progressive enhancement over framework-only assumptions.
Common Beginner Mistakes
- Assuming Web Components replace frameworks entirely.
- Using tag names without a hyphen.
- Doing too much DOM work in the constructor.
- Skipping cleanup when elements are removed from the page.
- Overusing Shadow DOM without a clear styling strategy.
- Ignoring keyboard support and ARIA for custom widgets.
- Building monolithic components instead of small focused ones.
Key Takeaways
- Web Components are browser-native standards, not a framework.
- Custom Elements, Shadow DOM, and Templates work together.
- You create reusable tags with JavaScript classes.
- Components work in plain HTML and inside React, Vue, and Angular.
- Lit and similar libraries build on the same underlying APIs.
- This tutorial series will take you from basics to production use.
Pro Tip
Build your first component in plain HTML and JavaScript before
adding Lit or a framework wrapper. Understanding the native APIs
makes every abstraction easier to debug later.