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:
name: The name of the attribute that changed.oldValue: The previous value of the attribute (returnsnullif the attribute is newly added).newValue: The updated value of the attribute (returnsnullif 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:
- Direct HTML Markup: Initial parsing of attributes defined directly on the element tag triggers the callback when the element is upgraded.
- JavaScript DOM APIs: Methods such as
element.setAttribute('theme', 'dark')orelement.removeAttribute('disabled'). - User Interaction / Framework Bindings: Any external script or framework updating the DOM node’s attributes.
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
- Guard Against Redundant Updates: Always check
if (oldValue !== newValue)inside the callback to prevent unnecessary re-renders or infinite loops when synchronizing attributes with properties. - Attribute-Property Reflection: If you create
JavaScript getters and setters for your attributes, use
setAttributewithin the setter so that property updates trigger theattributeChangedCallback. - String Values Only: HTML attributes are always
strings. If you need booleans or objects, convert the values inside the
callback accordingly (e.g., using
hasAttributefor boolean flags orJSON.parsefor complex data).