Observed Attributes
This lesson explains observed attributes in Web Components, how to watch attribute changes with attributeChangedCallback, and how to keep component UI in sync with declarative HTML.
What Are Observed Attributes?
Observed attributes are HTML attributes that a custom element
chooses to watch. When one of those attributes changes, the
browser calls attributeChangedCallback() so the
component can update its UI or internal state.
You declare them with the static
observedAttributes getter. This is a core part of
building reactive Web Components that respond to declarative HTML.
| Concept | Description |
observedAttributes | Static list of attributes to watch |
attributeChangedCallback() | Runs when an observed attribute changes |
| Trigger | setAttribute(), removeAttribute(), HTML updates |
| Arguments | name, oldValue, newValue |
| Best For | Declarative config from HTML and SSR |
| Works With | Properties, rendering, and accessibility state |
Basic Observed Attributes Example
class StatusBadge extends HTMLElement {
static get observedAttributes() {
return ["variant"];
}
connectedCallback() {
this.render();
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) return;
this.render();
}
render() {
const variant =
this.getAttribute("variant") || "info";
this.textContent = `Status: ${variant}`;
}
}
customElements.define("status-badge", StatusBadge);
<status-badge variant="success"></status-badge>
<script>
const badge = document.querySelector("status-badge");
badge.setAttribute("variant", "danger");
</script>
When variant changes, the component re-renders and
reflects the new attribute value.
How Observed Attributes Work
| Step | What Happens |
| 1. Declare watched names | Return attribute names from observedAttributes |
| 2. Attribute changes | Browser detects update on the host element |
| 3. Callback fires | attributeChangedCallback() is invoked |
| 4. Component reacts | Update UI, classes, ARIA, or internal state |
| 5. Render stays in sync | HTML and component output match |
Watching Multiple Attributes
class AppButton extends HTMLElement {
static get observedAttributes() {
return ["variant", "size", "disabled"];
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) return;
if (name === "variant") this.updateVariant(newValue);
if (name === "size") this.updateSize(newValue);
if (name === "disabled") this.updateDisabled(newValue);
}
}
List every attribute the component should react to. Handle each
change in one callback or delegate to focused update methods.
Boolean Observed Attributes
class TogglePanel extends HTMLElement {
static get observedAttributes() {
return ["open"];
}
attributeChangedCallback(name) {
if (name === "open") {
const isOpen = this.hasAttribute("open");
this.shadowRoot.querySelector(".panel").hidden = !isOpen;
this.setAttribute("aria-expanded", String(isOpen));
}
}
}
<toggle-panel open>
Panel content
</toggle-panel>
For boolean attributes, use hasAttribute() instead of
relying only on the attribute value string.
Initial Attributes and First Render
class AlertBox extends HTMLElement {
static get observedAttributes() {
return ["type", "message"];
}
connectedCallback() {
this.render();
}
attributeChangedCallback() {
if (!this.isConnected) return;
this.render();
}
render() {
const type = this.getAttribute("type") || "info";
const message = this.getAttribute("message") || "";
this.innerHTML = `
<div class="alert alert-${type}" role="alert">
${message}
</div>
`;
}
}
attributeChangedCallback() may run before the element
is connected to the DOM. Many components render once in
connectedCallback() and ignore disconnected updates.
attributeChangedCallback Arguments
attributeChangedCallback(name, oldValue, newValue) {
console.log(name); // "variant"
console.log(oldValue); // "info"
console.log(newValue); // "danger"
}
| Argument | Description |
name | Name of the changed attribute |
oldValue | Previous attribute value |
newValue | New attribute value |
Observed Attributes and Properties
class UserChip extends HTMLElement {
static get observedAttributes() {
return ["name", "role"];
}
get name() {
return this.getAttribute("name") || "";
}
set name(value) {
this.setAttribute("name", value);
}
attributeChangedCallback() {
this.render();
}
}
Observed attributes work best when properties and attributes stay
synchronized. Changing a property can update an attribute, which
then triggers the callback.
Attributes That Are Not Observed
// "hidden-ui" is NOT in observedAttributes
badge.setAttribute("hidden-ui", "true");
// attributeChangedCallback will NOT run
Only attributes listed in observedAttributes trigger
the callback. If you need to react to other attributes, add them
to the observed list.
When to Use Observed Attributes
- The component is configured through HTML attributes.
- You need SSR-friendly declarative APIs.
- Attribute changes should update UI or accessibility state.
- You reflect properties back to attributes.
- Frameworks or pages set config with
setAttribute(). - Variant, size, open, and disabled states come from markup.
Common Observed Attribute Use Cases
variant and size on buttons and badges. open on dialogs, drawers, and accordions. disabled on form controls. value and placeholder on inputs. type on alerts and status components. selected and active on tabs and menus.
Observed Attributes Best Practices
- Keep
observedAttributes small and intentional. - Compare
oldValue and newValue before re-rendering. - Render once in
connectedCallback() for initial state. - Use
hasAttribute() for boolean attributes. - Sync important properties and attributes consistently.
- Document which attributes are supported and what they do.
- Update ARIA and visual state from attribute changes.
Common Observed Attribute Mistakes
- Forgetting to list an attribute in
observedAttributes. - Assuming all attribute changes trigger the callback automatically.
- Re-rendering on every callback without checking value changes.
- Not handling initial attributes before the element connects.
- Treating boolean attributes like normal string values only.
- Observing too many attributes and causing unnecessary renders.
- Updating attributes inside the callback without guards, causing loops.
Key Takeaways
observedAttributes defines which attributes a component watches. attributeChangedCallback() reacts to those changes. - Observed attributes power declarative, HTML-driven component APIs.
- They work best with rendering, boolean state, and property sync.
- Careful observation keeps Web Components reactive and efficient.
Pro Tip
Treat observedAttributes as your component's
declarative config list. If an attribute is part of the public API
and should change component behavior, observe it and document it.