{"id":13571946,"url":"https://github.com/shawnbot/custom-elements","last_synced_at":"2025-04-12T22:40:28.497Z","repository":{"id":35001041,"uuid":"39091524","full_name":"shawnbot/custom-elements","owner":"shawnbot","description":"All about HTML Custom Elements","archived":false,"fork":false,"pushed_at":"2019-03-04T12:39:59.000Z","size":89,"stargazers_count":202,"open_issues_count":4,"forks_count":12,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-04T19:57:52.301Z","etag":null,"topics":["custom-elements","html","javascript","web-components"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shawnbot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-07-14T17:59:21.000Z","updated_at":"2025-01-18T13:08:26.000Z","dependencies_parsed_at":"2022-09-15T23:12:25.971Z","dependency_job_id":null,"html_url":"https://github.com/shawnbot/custom-elements","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shawnbot%2Fcustom-elements","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shawnbot%2Fcustom-elements/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shawnbot%2Fcustom-elements/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shawnbot%2Fcustom-elements/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shawnbot","download_url":"https://codeload.github.com/shawnbot/custom-elements/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248643045,"owners_count":21138353,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["custom-elements","html","javascript","web-components"],"created_at":"2024-08-01T14:01:08.766Z","updated_at":"2025-04-12T22:40:28.473Z","avatar_url":"https://github.com/shawnbot.png","language":null,"readme":"# All about HTML Custom Elements\nCustom Elements is a [WHATWG] HTML [specification][spec] that\nprovides a mechanism for defining new behaviors (such as dynamic\ncontent or interactivity) for HTML elements with custom names.\nCustom elements are just HTML elements, with all of the methods and\nproperties of other, built-in elements. The only real constraint\nis that\n**[custom element names must contain a hyphen (`-`)](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name)**.\n\n```html\n\u003cmessage-element\u003eHi!\u003c/message-element\u003e\n\n\u003csuper-section\u003e\n  \u003cp\u003eCustom elements can contain content!\u003c/p\u003e\n  \u003cmessage-element\u003eAnd other custom elements!\u003c/message-element\u003e\n\u003c/super-section\u003e\n```\n\n**Note:** The adopted [custom element spec][spec], formerly known\nas \"v1\", differs almost entirely from the original [\"v0\" spec][v0 spec].\nIf you've been using `document.registerElement()` from the v0 API,\nthen read on to see what's changed.\n\n## Table of Contents\n1. [Why custom elements?](#why-custom-elements)\n1. [How do they work?](#how-do-they-work)\n1. [Browser support](#browser-support)\n1. [Customized built-in elements](#customized-built-in-elements)\n1. [Observed attributes](#observed-attributes)\n1. [Polyfills](#polyfills)\n1. [Further Reading](#further-reading)\n\n## Why Custom Elements?\n* **Encapsulation.** Custom element names avoid ambiguity in\n  markup (versus, say, a `\u003cdiv\u003e` or `\u003cspan\u003e` with a \"special\"\n  class), and provide a solid foundation for scoped styles.\n\n  * If you've ever felt like it was wrong to define reuseable\n    components with \"special\" class names initialized by jQuery\n    selections that can only be called after the DOM is ready (or\n    rely on mutation observers to watch for new instances), then\n    custom elements could be your new jam.\n\n  * In native implementations (see [browser support](#browser-support)),\n    you can target custom elements before (`:unresolved` in the\n    [v0 spec]) and after (`:defined` in the [v1 spec][spec]) they've\n    been registered via JavaScript.\n\n* **The DOM _is_ the API.** Components built with tools like jQuery\n  can be cumbersome to build, modify, and maintain because they often\n  introduce another layer of abstraction (such as the `jQuery` object\n  and its API) on top of the DOM. And while there's nothing to stop\n  one from building custom elements that use jQuery, D3, React, or\n  whatever under the hood, I've found custom elements made with\n  vanilla JS to be easier to grok and read.\n\n  Put another way: **The DOM isn't going away any time soon**, and\n  custom elements provide a solid conceptual _and_ technical\n  foundation on which all sorts of amazing things can be built.\n\n* **Web Standards.** Custom elements are an adopted [WHATWG] HTML\n  [specification][spec]. That means that—in theory, at least—they\n  will _eventually_ be implemented natively in all modern browsers\n  with the same API.\n\n  Keep in mind is that **custom elements are not mutually exclusive\n  of other web component technologies**. In fact, I see them as a\n  powerful force multiplier of any technology that leverages them:\n  When designed well, custom elements put their power into the hands\n  of anyone who can write HTML. Great React components, for instance,\n  can be made even greater by packaging them up as custom elements.\n\n## How do they work?\nCustom element behaviors are added at runtime (whenever the\n\"registration\" occurs in JavaScript), and can hook into a number of\ndifferent element _lifecycle events_:\n\n1. **When an instance of the custom element is created**, either via\n   the class constructor or `document.createElement()`; or when\n   existing custom elements are registered.\n1. **When an instance is \"connected\"** to the document,\n   either directly or indirectly. This may be called multiple times,\n   and is generally the best place to add event handlers.\n1. **When an instance is removed** (or \"disconnected\") from the\n   document, either directly or indirectly. This may also be called\n   multiple times.\n1. **When an attribute is changed**. You can also subscribe to specific\n   [observed attributes](#observed-attributes) if you only care about\n   attributes unique to your element, or to implement [attribute reflection].\n\n## Browser Support\nAs of summer 2018:\n\n* [Chrome has implemented][chrome] both the [v0 spec] and the adopted [\"v1\" spec][spec].\n* [Safari has implemented][safari] so-called \"autonomous\" custom elements, but has\n  declined to support extension of built-in elements.\n* [Firefox has implemented][firefox] the spec behind a feature flag.\n\nRegardless of your support targets, you should use a [polyfill](#polyfills).\n\n⚠️ When using custom elements—or anything involving JavaScript,\nfor that matter—**always design experiences for progressive enhancement**,\nand plan for the possibility that JavaScript isn't enabled or available.\n\n## The API\nThe custom elements API consists mainly of a [CustomElementRegistry] object\nthat can be used to register class constructors for custom elements by name:\n\n```js\nwindow.customElements.define('element-name', ElementClass);\n```\n\nWhere `ElementClass` is a class that extends [HTMLElement]:\n\n```js\n// ES2015\nclass ElementClass extends HTMLElement {\n  constructor() {\n    super() // \u003c-- this is required!\n    this.created()\n  }\n  \n  created() {\n    console.log('hi!', this)\n  }\n}\n```\n\nCustom element classes may implement any of the following lifecycle\n(instance) methods:\n\n1. `connectedCallback()` is called whenever the element is added to\n   the document, either directly (`document.body.appendChild(el)`) or indirectly\n   (as part of a fragment or a DOM tree that's added to the document).\n1. `disconnectedCallback()` is called whenever the element is removed from\n   the document.\n1. `attributeChangedCallback(attr, oldValue, newValue)` is called when\n   an _observed_ attribute (see below) is changed.\n\nand the following static (class) properties:\n\n1. `observedAttributes` is an optional array of attribute names for\n   which the `attributeChangedCallback()` will be called. If you do not\n   provide this property, the callback will fire for all attributes.\n\n  ```js\n  // ES5\n  class CounterElement extends HTMLElement {\n    static get observedAttributes() { return ['value'] }\n\n    attributeChangedCallback(name, old, value) {\n      // we can safely ignore name here because 'value' is the only\n      // observed attribute\n      this.value = value\n    }\n        \n    get value() {\n      return ('_value' in this)\n        ? this._value\n        : (this._value = 0)\n    }\n    \n    set value(value) {\n      this._value = value\n    }\n  }\n  ```\n\n## Customized built-in elements\n\n⚠️ **Warning:** Safari does not _yet_ implement [this portion of the\nspec][customized built-ins]. If you wish to use it, you will need a [polyfill](#polyfills).\n\nCustom elements may extend built-in HTML elements with special\nsemantics or behaviors (such as `\u003cbutton\u003e` or `\u003cinput\u003e`). Here's how\nthey work:\n\n1. Register the element with an additional argument indicating which\n   element name it extends:\n   \n    ```js\n    window.customElements.define('fancy-button', FancyButton, {\n      extends: 'button'\n    })\n    ```\n\n1. Instantiate the element in HTML with the `is` attribute of the\n   extended built-in set to the name of the custom element:\n   \n    ```html\n    \u003c!-- this: --\u003e\n    \u003cbutton is=\"fancy-button\"\u003eI am fancy\u003c/button\u003e\n\n    \u003c!-- NOT this: --\u003e\n    \u003cfancy-button\u003eI am not fancy\u003c/fancy-button\u003e\n    ```\n  \n1. Instantiate the element in JavaScript by passing an additional\n   argument to `document.createElement()` with the `is` property\n   set to the name of the custom element:\n   \n    ```js\n    var fancy = document.createElement('button', {is: 'fancy-button'})\n    ```\n\n\n## Observed Attributes\nObserved attributes are attributes that fire the `attributeChangedCallback()`\nlifecycle method. If the custom element class has a (static)\n`observedAttributes` array, the callback will fire for _only_ the listed\nattributes:\n\n```js\n// ES2015\nclass CustomElement extends HTMLElement {\n  static get observedAttributes() { return ['foo', 'bar']; }\n  \n  attributeChangedCallback(attr, old, value) {\n    // `attr` will only ever equal 'foo' or 'bar'\n    switch (attr) {\n      case 'foo':\n        break\n      case 'bar':\n        break\n    }\n  }\n}\n```\n\nOtherwise, the callback will be fired for _all_ attributes.\n\n```js\n// ES2015\nclass CustomElement extends HTMLElement {\n  attributeChangedCallback(attr, old, value) {\n    // `attr` could be anything\n  }\n}\n```\n\n📝 When implementing [attribute reflection], please observe the\n[W3C API Design Principles](https://w3ctag.github.io/design-principles/#api-surface).\n\n## The Custom elements registry\nThe [CustomElementRegistry] object available at `window.customElements`\nhas two additional methods for querying its state and responding to when\nspecific custom elements are registered:\n\n* `customElements.get('element-name')` returns the class constructor\n  of the provided custom element name (or `undefined` if it hasn't been\n  defined).\n\n* `customElements.whenDefined('element-name')` returns a Promise that\n  resolves if/when the named custom element is defined via\n  `customElements.define()`.\n\n## Gotchas\n\n### Custom Events\nYou can listen for and dispatch [custom events][CustomEvent] in custom\nelements. The only bummer is that, even though most modern browsers\nsupport the `CustomEvent` constructor, it's missing in all versions of IE\nand in older versions of [PhantomJS], which is used for lots of \"headless\"\nintegration testing. My advice is to include\n[this polyfill](https://www.npmjs.com/package/custom-event), which falls\nback on the native implementation. Here's how you could have your component\n\"announce\" its readiness to the rest of the document, for instance:\n\n```js\nvar CustomEvent = require('custom-event');\ndocument.registerElement('my-element', {\n  prototype: Object.create(\n    HTMLElement.prototype,\n    {\n      attachedCallback: {value: function() {\n        this.dispatchEvent(new CustomEvent('my-element-ready'));\n      }}\n    }\n  )\n});\n```\n### SVG and Namespaces\nBecause you need a [polyfill](#polyfills) and namespaces are tricky, it's\nbasically impossible to reliably extend SVG elements, or any element that\nrequires an XML namespace. Your best bet is to write a component that wraps\n`\u003csvg\u003e` elements or creates them at runtime if they don't exist.\n\n### Class Definition\nOne of the trickiest things about custom elements is the magical incantation\nfor defining element classes that extend `HTMLElement` or its subclasses,\nespecially in \"legacy\" ES5 environments that don't support the `class` keyword\nor `super()` calls. There are a couple of ways to pull it off:\n\n1. Create an object literal (rather than a proper constructor function) with\n   a `prototype` that extends `HTMLElement.prototype`. The only way to do this\n   in a single expression is to use [`Object.create()`][Object.create], which\n   extends the first argument with _descriptors_ in the second. The important\n   thing to note here is that because these are property descriptors, methods\n   must be provided as objects with a `value` property:\n\n    ```js\n    // ES5\n    var CustomElement = {\n      prototype: Object.create(\n        HTMLElement.prototype,\n        {\n          // this will NOT work:\n          createdCallback: function() {\n          },\n          // but this will:\n          createdCallback: {value: function() {\n          }},\n\n          // accessors look like this:\n          someValue: {\n            get: function() { /* ... */ },\n            set: function(value) { /* ... */ }\n          }\n        }\n      )\n    };\n    ```\n\n    **Note:** if you need to support older browsers such as IE8 or below,\n    you will also need a polyfill or shim for ES5 standard APIs, such as\n    [aight] or [es5-shim].\n  \n1. A variation on the above method uses [`Object.create()`][Object.create]\n   but assigns methods directly:\n\n    ```js\n    // ES5\n    var CustomElement = {\n      prototype: Object.create(HTMLElement.prototype)\n    };\n\n    CustomElement.prototype.someMethod = function(arg) { /* ... */ };\n\n    // any accessors not passed to Object.create() can be defined like so.\n    // note that this is *exactly* what Object.create() is doing under the\n    // hood!\n    Object.defineProperties(CustomElement.prototype, {\n      someValue: {\n        get: function() { /* ... */ },\n        set: function(value) { /* ... */ }\n      }\n    });\n    ```\n\n1. Use [Babel] and the [custom-element-classes transform](https://github.com/github/babel-plugin-transform-custom-element-classes#readme). Your `.babelrc` should look something like this:\n\n    ```json\n    {\n      \"presets\": [\"env\"],\n      \"plugins\": [\n        \"transform-custom-element-classes\"\n      ]\n    }\n    ```\n    \n    which should make it possible to write classes like:\n    \n    ```js\n    class Widget extends HTMLElement {\n      constructor() {\n        super()\n      }\n    }\n    \n    window.customElements.define('widget-element', Widget)\n    ```\n\n## Polyfills\nThe semi-official [webcomponents/custom-elements] polyfill is what GitHub uses,\nand it provides a bunch of workarounds for the spec rules involving class\nconstructors and the `new` keyword. You should use it, too!\n\n\n## Further reading\n* [Custom Elements v1: Reusable Web Components (Google)](https://developers.google.com/web/fundamentals/web-components/customelements) is a great introduction to custom elements.\n* [Introducing Custom Elements (WebKit)](https://webkit.org/blog/7027/introducing-custom-elements/) contains some nice implementation tips. \n* [Using Custom Elements (MDN)](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) is, like pretty much everything else on MDN, a solid reference.\n* [Custom Elements Everywhere](https://custom-elements-everywhere.com/) rates popular web frameworks for compatibility with custom elements.\n* The [WebComponents.org introduction](https://www.webcomponents.org/introduction) explains how custom elements fit into the broader landscape of native web components and complement technologies like the Shadow DOM and `\u003ctemplate\u003e` elements.\n\n[spec]: https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element\n[old spec]: https://www.w3.org/TR/custom-elements/\n[v0 spec]: https://www.w3.org/TR/2016/WD-custom-elements-20160226/\n[caniuse]: https://caniuse.com/#feat=custom-elementsv1\n[HTMLElement]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement\n[CustomElementRegistry]: https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry\n[Object.create]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create\n[aight]: https://github.com/shawnbot/aight\n[es5-shim]: https://github.com/es-shims/es5-shim\n[Chrome]: https://www.chromestatus.com/feature/4696261944934400\n[Firefox]: https://bugzilla.mozilla.org/show_bug.cgi?id=889230\n[Safari]: https://bugs.webkit.org/show_bug.cgi?id=150225\n[PhantomJS]: http://phantomjs.org/\n[CustomEvent]: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent\n[attribute reflection]: https://github.com/domenic/webidl-html-reflector\n[event delegation]: https://davidwalsh.name/event-delegate\n[custom element registry]: https://html.spec.whatwg.org/multipage/custom-elements.html#customized-built-in-element\n[WHATWG]: https://whatwg.org/faq#what-is-the-whatwg\n[customized built-ins]: https://html.spec.whatwg.org/multipage/custom-elements.html#customized-built-in-element\n[Babel]: http://babeljs.io/\n[webcomponents/custom-elements]: https://github.com/webcomponents/custom-elements#readme\n","funding_links":[],"categories":["Others","Standards"],"sub_categories":["Custom Elements"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshawnbot%2Fcustom-elements","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshawnbot%2Fcustom-elements","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshawnbot%2Fcustom-elements/lists"}