In short
Web components let you create your own reusable HTML elements, like <info-card>. Learn custom elements, template and slot, and shadow DOM in plain words, with short examples. What are web components? Imagine you build the same "profile card" on ten pages.
Web components let you create your own reusable HTML elements, like <info-card>. Learn custom elements, template and slot, and shadow DOM in plain words, with short examples.
What are web components?
Imagine you build the same "profile card" on ten pages. Each time you copy the same <div> structure, the same classes and the same CSS. Web components let you package that card into your own HTML tag, such as <profile-card>, and then use it like any built-in element.
Web components are built into every modern browser. You don't need a framework like React or Vue. They are made of three parts that work together:
- Custom elements: your own tag names, with JavaScript that says how they behave.
- The template element: HTML that is stored but not shown until you use it. You met
<template>in the data attributes lesson. - Shadow DOM: a private area inside your element, with its own HTML and CSS that the rest of the page cannot accidentally change.
How to make a custom element with customElements.define
Here is the smallest useful custom element. It reads a name attribute and shows a greeting.
<greeting-badge name="Amira"></greeting-badge>
<greeting-badge></greeting-badge>
<script>
class GreetingBadge extends HTMLElement {
connectedCallback() {
const name = this.getAttribute("name") ?? "guest";
this.textContent = `Hello, ${name}!`;
}
}
customElements.define("greeting-badge", GreetingBadge);
console.log(customElements.get("greeting-badge") === GreetingBadge);
// Output: true
</script>
The page shows "Hello, Amira!" and then "Hello, guest!". Line by line:
class GreetingBadge extends HTMLElementcreates a new kind of element.extends HTMLElementmeans "start with everything a normal HTML element can do".connectedCallback()is a special method. The browser runs it each time the element is added to the page.thisis the element itself.this.getAttribute("name")reads itsnameattribute.?? "guest"means "if there is no name, use guest".this.textContent = ...puts the greeting text inside the element, using a template literal.customElements.define("greeting-badge", GreetingBadge)registers the tag name and connects it to the class. After this line, every<greeting-badge>on the page comes to life.
Common mistake: Using a name without a dash, like
<greeting>. Custom element names must contain a hyphen, such asgreeting-badge. The dash guarantees your tag will never clash with a future built-in HTML element. They also need a closing tag;<greeting-badge />does not work.
Template, slot and shadow DOM in plain words
The DOM is the browser's live family tree of every element on your page. Shadow DOM is a small, separate tree attached to one element. Think of a custom element as a house: the shadow DOM is the inside, with its own furniture and paint. CSS from outside does not leak in, and the CSS inside does not leak out. A rule like h2 { color: red; } on your page will not change the <h2> inside the component.
A slot is a gap in the component where you can put your own content, like a picture frame. The frame (the component) stays the same; you choose the picture. A named slot like <slot name="title"> is filled by a child with slot="title". An unnamed slot takes all the other content. Text inside the <slot> tag is the default, shown if you don't fill it.
<template id="info-card-template">
<style>
.card { border: 2px solid #6a5acd; border-radius: 12px; padding: 1rem; max-width: 300px; }
h2 { margin: 0 0 0.5rem; color: #6a5acd; font-size: 1.25rem; }
</style>
<div class="card">
<h2><slot name="title">Untitled</slot></h2>
<slot>No description yet.</slot>
</div>
</template>
<info-card>
<span slot="title">HTML Basics</span>
<p>Learn tags, elements and attributes in 30 minutes.</p>
</info-card>
<info-card>
<span slot="title">CSS Flexbox</span>
</info-card>
<script>
class InfoCard extends HTMLElement {
constructor() {
super();
const template = document.querySelector("#info-card-template");
const shadow = this.attachShadow({ mode: "open" });
shadow.append(template.content.cloneNode(true));
}
}
customElements.define("info-card", InfoCard);
</script>
You see two purple-bordered cards. The first shows "HTML Basics" as its heading and your paragraph below. The second shows "CSS Flexbox" and the default text "No description yet." Here is what the script does:
super()must come first in a constructor; it sets up the normal element.document.querySelector(...)finds the template.this.attachShadow({ mode: "open" })creates the private shadow DOM.openmeans your own scripts can still look inside if needed.template.content.cloneNode(true)makes a full copy of the template's HTML, andshadow.append(...)puts it inside the shadow DOM.
Note: Your slotted content (the
<span>and<p>) stays in the normal page, so search engines and screen readers read it as usual. There is also a way to create shadow DOM with HTML only, using<template shadowrootmode="open">inside the element, which works in all modern browsers.
When should you use web components?
Web components are a good choice when:
- You want a reusable widget (a rating stars element, a share button, a card) that works on any site, with or without a framework.
- Many teams or projects need the same piece of UI.
- You want the component's CSS protected from the rest of the page.
They are not always needed. For a small site, plain HTML with a CSS class is simpler. And remember: content inside a custom element depends on JavaScript. Keep important text in the light DOM (inside the tags, through slots) and use real buttons, links and labels inside the component, so it stays accessible.
Practice exercise
Build a <user-avatar> custom element. It reads name and src attributes and shows an <img> with the name as alt text, followed by the name in a <strong>. Use it three times with different people.
Hint: in connectedCallback, build the HTML with a template literal and set this.innerHTML. Only do this with your own trusted values, never with text typed by visitors.
Key takeaways
- Web components are built into browsers and let you create reusable custom HTML elements.
- Register an element with
customElements.define("name-with-dash", ClassName). connectedCallbackruns when the element is added to the page.- Shadow DOM keeps the component's HTML and CSS private;
<slot>lets users insert their own content. - Use them for reusable widgets; plain HTML and CSS are fine for simple pages.