Skip to content

connectedCallback

This lesson explains connectedCallback with clear examples, use cases, and best practices so you can build reusable Web Components confidently.

What Is connectedCallback?

connectedCallback() is a Web Component lifecycle method that runs every time a custom element is added to the document.

It is commonly used to render markup, attach event listeners, read attributes, start timers, fetch data, or initialize component behavior.

Concept Description
connectedCallback() Runs when the element is inserted into the DOM
Lifecycle Type Custom Element lifecycle callback
Common Use Render UI, add events, initialize state, fetch data
Runs Multiple Times? Yes, if the element is removed and added again
Paired With disconnectedCallback() for cleanup
Main Benefit Safely initializes component logic after DOM connection

Basic connectedCallback Example

class HelloMessage extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <p>Hello from Web Component!</p>
    `;
  }
}

customElements.define("hello-message", HelloMessage);
<hello-message></hello-message>

When <hello-message> is added to the page, connectedCallback() runs and renders the paragraph.

How connectedCallback Works

Step What Happens
1. Custom element is defined customElements.define() registers the component
2. Element appears in DOM The browser finds the custom element on the page
3. Browser upgrades element The class logic is connected to the HTML element
4. Element connects connectedCallback() is called
5. Component initializes Markup, events, state, or data logic can run

Constructor vs connectedCallback

Feature constructor() connectedCallback()
Runs When Element instance is created Element is added to the DOM
Best For Initial setup, Shadow DOM creation Rendering, events, DOM-based logic
Can Read Attributes? Not always recommended Yes, commonly used
Can Access Children? Not safely Yes, usually safer

connectedCallback With Shadow DOM

class AppButton extends HTMLElement {
  constructor() {
    super();

    this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        button {
          padding: 0.5rem 1rem;
          border-radius: 0.5rem;
          border: 1px solid #0d6efd;
          background: #0d6efd;
          color: white;
        }
      </style>

      <button type="button">
        <slot>Click Me</slot>
      </button>
    `;
  }
}

customElements.define("app-button", AppButton);
<app-button>Save</app-button>

The constructor creates the Shadow DOM. The connectedCallback() method renders the component UI after the element is connected to the page.

Reading Attributes in connectedCallback

class UserBadge extends HTMLElement {
  connectedCallback() {
    const name = this.getAttribute("name") || "Guest";
    const role = this.getAttribute("role") || "User";

    this.innerHTML = `
      <div class="badge">
        <strong>${name}</strong>
        <span>${role}</span>
      </div>
    `;
  }
}

customElements.define("user-badge", UserBadge);
<user-badge name="Ayaan" role="Student"></user-badge>

Attributes are often read inside connectedCallback() because the element is already available in the document.

Adding Event Listeners in connectedCallback

class CounterButton extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
  }

  connectedCallback() {
    this.innerHTML = `
      <button type="button">Clicked 0 times</button>
    `;

    this.querySelector("button").addEventListener("click", () => {
      this.count++;
      this.querySelector("button").textContent =
        `Clicked ${this.count} times`;
    });
  }
}

customElements.define("counter-button", CounterButton);
<counter-button></counter-button>

connectedCallback() is a common place to attach event listeners because the component markup has already been rendered.

Cleanup With disconnectedCallback

class WindowSizeTracker extends HTMLElement {
  connectedCallback() {
    this.handleResize = () => {
      this.textContent = `Width: ${window.innerWidth}`;
    };

    window.addEventListener("resize", this.handleResize);
    this.handleResize();
  }

  disconnectedCallback() {
    window.removeEventListener("resize", this.handleResize);
  }
}

customElements.define("window-size-tracker", WindowSizeTracker);

If connectedCallback() adds global event listeners, timers, or subscriptions, clean them up inside disconnectedCallback().

connectedCallback Can Run Multiple Times

class SimplePanel extends HTMLElement {
  connectedCallback() {
    console.log("Panel connected");
  }
}

customElements.define("simple-panel", SimplePanel);

const panel = document.createElement("simple-panel");

document.body.appendChild(panel);
panel.remove();
document.body.appendChild(panel);

In this example, connectedCallback() runs each time the same element is added back to the document.

Avoid Duplicate Initialization

class SafeCard extends HTMLElement {
  connectedCallback() {
    if (this.hasInitialized) {
      return;
    }

    this.hasInitialized = true;

    this.innerHTML = `
      <article>
        <h2>Profile Card</h2>
        <p>This card renders only once.</p>
      </article>
    `;
  }
}

customElements.define("safe-card", SafeCard);

Use a flag when your component should initialize only once, especially if it may be moved around in the DOM.

Fetching Data in connectedCallback

class ProductInfo extends HTMLElement {
  async connectedCallback() {
    this.innerHTML = `<p>Loading product...</p>`;

    const productId = this.getAttribute("product-id");

    const response = await fetch(`/api/products/${productId}`);
    const product = await response.json();

    this.innerHTML = `
      <article>
        <h2>${product.name}</h2>
        <p>${product.description}</p>
      </article>
    `;
  }
}

customElements.define("product-info", ProductInfo);

You can fetch data inside connectedCallback(), but handle loading states and errors properly in production components.

When to Use connectedCallback

  • Render initial HTML for a custom element.
  • Read attributes after the element is connected.
  • Attach event listeners to internal elements.
  • Start timers, observers, or subscriptions.
  • Fetch data needed for the component UI.
  • Initialize logic that depends on the document.

Common connectedCallback Use Cases

  • Rendering cards, buttons, modals, tabs, and menus.
  • Initializing Shadow DOM content.
  • Adding click, input, submit, or keyboard events.
  • Reading attributes like type, label, and value.
  • Starting API calls when the component appears.
  • Connecting to observers such as ResizeObserver or IntersectionObserver.

connectedCallback Best Practices

  • Use the constructor for basic setup and Shadow DOM creation.
  • Use connectedCallback() for DOM-related initialization.
  • Remember that connectedCallback() can run more than once.
  • Clean up listeners, timers, and observers in disconnectedCallback().
  • Avoid rendering duplicate markup on repeated connections.
  • Keep heavy logic separate in helper methods.
  • Handle loading and error states when fetching data.

Common connectedCallback Mistakes

  • Assuming connectedCallback() runs only once.
  • Adding duplicate event listeners every time the element reconnects.
  • Not cleaning up global event listeners.
  • Doing too much work directly inside the lifecycle method.
  • Using innerHTML repeatedly and losing event listeners.
  • Trying to access child content too early in the constructor.
  • Forgetting to handle missing attributes or API errors.

Key Takeaways

  • connectedCallback() runs when a custom element enters the DOM.
  • It is ideal for rendering, events, attribute reading, and setup logic.
  • It can run multiple times during the element lifecycle.
  • Use disconnectedCallback() to clean up side effects.
  • Good lifecycle management makes Web Components reliable and reusable.

Pro Tip

Treat connectedCallback() as the place where your component becomes active. Render the UI, attach events, and start external work there, but always clean up long-running side effects in disconnectedCallback().