Define Custom Elements
This lesson explains Define Custom Elements with clear examples, use cases, and best practices so you can build reusable Web Components confidently.
What Does Define Elements Mean?
Defining elements means registering a custom HTML tag with the browser
using the customElements.define() method.
After an element is defined, the browser knows how to create, upgrade, and
run the JavaScript class connected to that custom element.
| Concept | Description |
customElements.define() | Registers a custom element with the browser |
| Element Name | Custom tag name such as app-button |
| Element Class | JavaScript class that controls the element behavior |
| Custom Element Registry | Browser registry that stores defined custom elements |
| Upgrade | Browser connects existing HTML elements to their class after definition |
| Main Benefit | Create reusable custom HTML elements for web applications |
Basic Define Element Example
class AppMessage extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<p>Hello from a defined custom element!</p>
`;
}
}
customElements.define("app-message", AppMessage);
<app-message></app-message>
The element name app-message is connected to the
AppMessage class using customElements.define().
customElements.define() Syntax
customElements.define(name, constructor, options);
| Parameter | Description | Example |
name | The custom element tag name | "user-card" |
constructor | The class used to create the element | UserCard |
options | Optional object for customized built-in elements | { extends: "button" } |
How Defining Elements Works
| Step | What Happens |
| 1. Create class | Extend HTMLElement or a built-in element class |
| 2. Add behavior | Use lifecycle callbacks, methods, attributes, or Shadow DOM |
| 3. Define element | Call customElements.define() |
| 4. Browser registers it | The name and class are stored in the Custom Element Registry |
| 5. Element upgrades | Matching HTML tags become active custom elements |
Custom Element Naming Rules
| Rule | Valid Example | Invalid Example |
| Must contain a hyphen | app-button | button |
| Use lowercase names | user-card | UserCard |
| Avoid native tag names | site-header | header |
| Use meaningful prefixes | shop-product-card | x-card |
A hyphen is required so browsers can distinguish custom elements from
built-in HTML elements.
Element Upgrade Example
<user-greeting name="John"></user-greeting>
<script>
class UserGreeting extends HTMLElement {
connectedCallback() {
const name = this.getAttribute("name") || "Guest";
this.innerHTML = `
<p>Hello, ${name}!</p>
`;
}
}
customElements.define("user-greeting", UserGreeting);
</script>
The browser may see <user-greeting> before the class is
registered. Once customElements.define() runs, the element is
upgraded and its lifecycle callbacks run.
Checking If an Element Is Defined
const ExistingElement = customElements.get("app-message");
if (ExistingElement) {
console.log("app-message is already defined");
}
Use customElements.get() to check whether a custom element has
already been registered.
Avoid Duplicate Definitions
if (!customElements.get("app-message")) {
customElements.define("app-message", AppMessage);
}
Defining the same element name more than once causes an error. Checking
first prevents duplicate registration problems.
Waiting Until an Element Is Defined
customElements.whenDefined("user-card").then(() => {
console.log("user-card is ready to use");
});
The customElements.whenDefined() method returns a Promise that
resolves after the element has been registered.
Defining an Element With Shadow DOM
class InfoCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
.card {
border: 1px solid #dee2e6;
border-radius: 1rem;
padding: 1rem;
background: #ffffff;
}
h2 {
color: #0d6efd;
margin-top: 0;
}
</style>
<article class="card">
<h2><slot name="title">Info</slot></h2>
<div><slot></slot></div>
</article>
`;
}
}
customElements.define("info-card", InfoCard);
<info-card>
<span slot="title">Profile</span>
<p>This card uses Shadow DOM.</p>
</info-card>
After the element is defined, the browser connects the
<info-card> tag to the InfoCard class and
renders the Shadow DOM content.
Defining Customized Built-In Elements
class PrimaryButton extends HTMLButtonElement {
connectedCallback() {
this.classList.add("btn", "btn-primary");
}
}
customElements.define("primary-button", PrimaryButton, {
extends: "button"
});
<button is="primary-button">
Save
</button>
Customized built-in elements extend native elements, but autonomous custom
elements like <app-button> are more commonly used in Web
Components.
Autonomous vs Customized Built-In Elements
| Type | Example | Define Syntax |
| Autonomous Custom Element | <app-button> | customElements.define("app-button", AppButton) |
| Customized Built-In Element | <button is="primary-button"> | customElements.define("primary-button", PrimaryButton, { extends: "button" }) |
When to Define Custom Elements
- You want to create a reusable custom HTML tag.
- You need a component that works without a specific framework.
- You want to package behavior, markup, and styles together.
- You are building a design system or component library.
- You want a browser-native component API.
- You need components that can be used across multiple applications.
Common Define Element Use Cases
- Define UI components like
app-button, user-card, and site-header. - Register design system components for reuse.
- Create framework-independent widgets.
- Upgrade server-rendered custom element tags after JavaScript loads.
- Build micro frontend UI blocks shared across teams.
- Expose reusable browser-native components in a package.
Define Elements Best Practices
- Use clear, lowercase, hyphenated custom element names.
- Check with
customElements.get() before defining shared components. - Use
customElements.whenDefined() when code depends on a registered component. - Prefer autonomous custom elements for broad browser compatibility and simpler usage.
- Keep element class names readable and aligned with tag names.
- Define elements once at module load time.
- Document the element name, attributes, properties, events, slots, and parts.
Common Define Element Mistakes
- Defining a custom element name without a hyphen.
- Calling
customElements.define() more than once for the same name. - Using native element names like
button or header. - Forgetting to import the JavaScript module that defines the element.
- Expecting the custom element to work before it is registered.
- Using unclear names that conflict across teams or packages.
- Not documenting the custom element public API.
Key Takeaways
- Use
customElements.define() to register a custom element. - Custom element names must include a hyphen.
- The browser stores definitions in the Custom Element Registry.
- Existing matching tags are upgraded after definition.
- Use
customElements.get() to avoid duplicate definitions. - Use
customElements.whenDefined() to wait for registration.
Pro Tip
Treat customElements.define() as the public registration point
of your Web Component. Define each element once, use stable names, and
document the API clearly for consumers.