Custom Attributes polyfill

Checking support...

What is this?

Custom Attributes are "Custom Elements, but for attributes". You subclass Attr, register it under a hyphenated name, and every matching attribute on a connected element is upgraded in place to an instance of your class, with lifecycle callbacks mirroring custom elements. Where a custom element models what an element is, a custom attribute models a behaviour an element has, and can be applied to any element: HTML, SVG, MathML, or another custom element.

class PersistValue extends Attr {
  connectedCallback() {
    const stored = localStorage.getItem(this.value);
    if (stored !== null) this.ownerElement.value = stored;
    this.ownerElement.addEventListener("input", this);
  }

  disconnectedCallback() {
    this.ownerElement.removeEventListener("input", this);
  }

  attributeChangedCallback(oldValue, newValue) {
    if (oldValue !== null) localStorage.removeItem(oldValue);
  }

  handleEvent() {
    localStorage.setItem(this.value, this.ownerElement.value);
  }
}

customAttributes.define("persist-value", PersistValue);
<input name="email" persist-value="email-draft" />
<textarea name="bio" persist-value="bio-draft"></textarea>

el.getAttributeNode("persist-value") and el.attributes["persist-value"] return the PersistValue instance, so methods added to the class live on the attribute node and never clash with element API.

Lifecycle callbacks

CallbackRuns when
constructor()the attribute is upgraded, constructed directly, or created by document.createAttribute()
attributeChangedCallback(oldValue, newValue)the value is added, changed or removed; once on upgrade with its initial value
connectedCallback()added to a connected element, or its element is inserted into a document
disconnectedCallback()removed from a connected element, or its element is removed
connectedMoveCallback()its element is moved with moveBefore()
adoptedCallback(oldDocument, newDocument)its element is adopted into another document

Using the polyfill

import "custom-attributes-polyfill";

Or apply it manually:

import { isSupported, apply } from "custom-attributes-polyfill/fn";
if (!isSupported()) apply();

The polyfill covers window.customAttributes, CustomAttributeRegistry (including scoped registries for shadow trees), the Attr constructor, document.createAttribute(), and the customAttributeRegistry option on createElement(), importNode(), attachShadow() and setHTMLUnsafe().

Limitations

See the README for the full list.