Skip to content

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.