How attributeChangedCallback Works in Web Components

The attributeChangedCallback is a native lifecycle method in JavaScript Web Components that allows custom elements to monitor and react to changes in their HTML attributes. This article explains how this callback operates, how to configure the required observedAttributes whitelist, how the callback captures previous and current values, and best practices for synchronizing attributes with component state.

The Role of observedAttributes

By default, a custom element does not monitor every attribute modification for performance reasons. To enable the attributeChangedCallback, you must define a static getter named observedAttributes on the custom element class. This getter returns an array of attribute names that the browser should watch.

class MyElement extends HTMLElement {
  static get observedAttributes() {
    return ['theme', 'disabled'];
  }
}

If an attribute is modified but is not listed in observedAttributes, the browser ignores the change and does not invoke the callback.

The attributeChangedCallback Signature

Whenever an observed attribute is added, modified, or removed, the browser automatically executes attributeChangedCallback with three arguments:

  1. name: The name of the attribute that changed.
  2. oldValue: The previous value of the attribute (returns null if the attribute is newly added).
  3. newValue: The updated value of the attribute (returns null if the attribute was removed).
attributeChangedCallback(name, oldValue, newValue) {
  if (oldValue === newValue) return;

  if (name === 'theme') {
    this.updateTheme(newValue);
  }
}

How Changes Are Triggered

The callback responds to attribute modifications made through several methods:

Complete Implementation Example

Here is a practical implementation of a custom component that reacts to attribute changes:

class StatusBadge extends HTMLElement {
  static get observedAttributes() {
    return ['status'];
  }

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue !== newValue && name === 'status') {
      this.render();
    }
  }

  render() {
    const status = this.getAttribute('status') || 'offline';
    this.textContent = `Status: ${status.toUpperCase()}`;
    this.className = `badge-${status}`;
  }
}

customElements.define('status-badge', StatusBadge);

Key Considerations