Web components

← Back

Custom elements

We use custom elements to encapsulate JavaScript behaviors and share them across different projects. Since custom element APIs are native to the browser, they come with no framework dependencies and are universally adaptable.

When designing a custom element, we try to mimic how a native HTML element behaves. For example, a form participating custom element should have a value content and IDL attribute.

Our general philosophy of striving for progressive enhancement extends to custom elements as well. We keep as much of the content in markup as possible and only add behaviors on top of that. For example, <local-time> shows the raw timestamp by default and gets upgraded to translate the time to the local timezone, while <details-dialog>, when nested in the <details> element, is interactive even without JavaScript, but gets upgraded with accessibility enhancements.

Being dependent on markup also means we avoid reinventing the wheel wherever possible. We should never be re-implementing <input> from scratch; instead, we depend on <input> and use a custom element wrapper to add behaviors. See <auto-complete> for an example.

We have a boilerplate for custom elements, which includes build steps, tests, demo pages, and linters.

Common pitfalls

connectedCallback is invoked each time the Custom Element is appended into a document-connected element. This will happen each time the node is moved, and may happen before the element's contents have been fully parsed. (from MDN)

If your element depends on its child elements heavily, consider using MutationObserver. This behaves correctly in Chrome but not in Firefox & Safari where connectedCallback is fired when DOM contents are partially parsed depending on load speed. More discussions here:

Shadow DOM

We do not use Shadow DOM because: