-
Notifications
You must be signed in to change notification settings - Fork 25
Custom Elements
[Add TOC Here]
Please review "Building Components" and "Best Practices" from Google before continuing.
HTML spec prohibits returning an instance containing attributes from constructor()
.
- Double check that logic in
constructor()
does not callthis.setAttribute()
.
The getElementById
function is only available on the document
object. If this.getRootElement().getElementById()
were called before the element instance has connected to the DOM, this.getRootElement()
will return the element instance (not the document) and getElementById
will be undefined.
- Use
querySelector()
instead.
For every watched attribute, a handler should be defined that reacts to changes of the attribute value.
- Remove entries from
observedAttributes
without corresponding logic inattributeChangedCallback()
.
Use Properties and Attributes to get/set configurations of a custom element.
- Do not use Attributes for complex primitives (Object, Array, etc.)
- Attributes always consumed as Strings in the element definition.
- You may have to coerce certain values to other native primitives.
Attributes are always consumed as strings in the element definition. You'll have to coerce certain values to other native primitives.
While you might be able to serialize certain objects into JSON format, it is highly discouraged because:
- it can be computationally expensive to serialize/deserialize objects
- javascript references within objects will be lost in the serialization process
- Use properties to apply complex primitive configuration values.
Events provide "callback-like" logic for application logic to react to changes. This provides a consistent API for client-side libraries to use in synchronizing overall application state.
-
Emit an event when a user triggers a significant action
- e.g., "open", "close", "dismiss", "retry", etc.
-
A property should exist for every HTML attribute.
- If it can be configured in HTML, it should be configurable via JavaScript.
-
Do not create HTML attributes for every JavaScript property.
- Not everything that can be configured in JavaScript should be configurable in HTML attributes.
- Some properties may be impossible to configure via HTML attributes.
-
Properties should be named similar to their HTML attribute
- e.g., avoid defining a
valid
property that reflects to theinvalid
attribute
- e.g., avoid defining a
Using methods to modify state adds unnecessary complexity to both implementation and consumption.
AVOID | RECOMMEND |
---|---|
elReveal.open() |
elReveal.open = true; |
elReveal.close() |
elReveal.open = false; |
elReveal.toggle() |
elReveal.open = !elReveal.open; |
If a user can do it, I should be able to do the same via JavaScript.
Define public methods for any functionality that a user can trigger. This has the added benefit of providing an API that can simplify certain testing strategies.
Example
elSearch.clear() // same as user clicking "X" to clear the value
elDisclosure.click() // same as user triggering a disclosure
Theming should be handled in helix-ui.css
, either via custom properties or
direct element styling. This keeps branding in an easily replaceable stylesheet.
Custom elements should not alter the flow of the document that they are included within. If they are meant to consume a certain geometry after upgrade, they should also consume the same geometry before upgrade.
Generally, it is not a good idea to alter the styles of elements in the parent document from within the ShadowDOM of a custom element. This becomes difficult to debug and can be very frustrating for consumers to implement.
The ::slotted()
selector is available, but it has limitations on what can be
styled and it has lower specificity/priority than LightDOM CSS.
Elements whose appearance are being handled purely within the ShadowDOM should try to implement overridable styles using CSS Variables (i.e., "Custom Properties").
While not all browsers support custom properties, fallback styles can be implemented.
#shadowElement {
/* Legacy Fallback */
border-color: #777;
/* Modern Browsers */
border-color: var(--border-color, #777);
}
Let's start by defining some terms:
-
Vanilla Control
- HTML element used to submit data via
<form>
functionality (e.g.,<input>
,<select>
,<textarea>
, etc.) - Appearance and behavior exists entirely in LightDOM.
- HTML element used to submit data via
-
Custom Control
- Custom HTML element that encapsulates all control state within a ShadowDOM
- Appearance and behavior exists entirely in ShadowDOM.
-
Hybrid Control
- Custom HTML element that styles a vanilla control, by encapsulating markup and styles within a ShadowDOM.
- Styling applied in ShadowDOM (maybe some LightDOM, too)
- Vanilla control behavior remains in LightDOM
Keep all submittable controls in the LightDOM.
Currently, no APIs exist to tap into the hardcoded behavior of <form>
.
Native <form>
elements are not aware of the vanilla controls contained within
the ShadowDOM of a custom control. This is because the <form>
element is
hard coded to serialize data values of vanilla controls within itself. If a
vanilla control isn't present in the LightDOM, the <form>
isn't aware of it,
and its data will not be included in the payload when the form is submitted.
Using vanilla controls in the ShadowDOM will be subject to issues mentioned above, but we can piggyback off of their existing functionality to modify internal state of a custom element.
- Bubbling events that originate from within the ShadowDOM will retarget the
custom element.
- No need to emit custom events
- Non-bubbling events will need to be re-emitted from the custom element.
- e.g.
blur
,focus
,scroll
, etc.
- e.g.
- Built-in browser heuristics, accessibility, and functionality can be leveraged
to help provide needed functionality.
- e.g., tab order, keyboard interaction, etc.
Example: <hx-search>
<!-- Shadow DOM -->
<div id="wrapper">
<input type="text" />
<button id="clear">
<hx-icon type="times"></hx-icon>
</div>
</div>
With the above markup, we can show or hide the button#clear
as a user enters
text into the input. By adding a 'click' event listner on the button, we can
clear the value and return focus to the input. Combined with some styling, the
user will only see the custom element, but they'll be interacting with the
vanilla <input>
and <button>
elements (win-win for accessibility,
appearance, and behavior).
NEEDS REVIEW
[Add flowchart here]
SEEK
-
Use semantic HTML elements as much as possible
- Most HTML elements have accessibility features baked into their implementation that cannot be imitated by custom elements.
-
Create a custom element if
- you need to define behavior that isn't available in semantic HTML elements
- e.g., Tabs, Menus, Popovers, Modals, etc.
- you need to decorate Light DOM content with static markup
- e.g.,
<hx-error>
- e.g.,
- you need to define behavior that isn't available in semantic HTML elements
AVOID
-
Avoid creating empty custom elements
- (i.e., no JavaScript prototype, no
customElements.define()
). - The lack of documentation for these elements is confusing to consumers.
- (i.e., no JavaScript prototype, no
-
Avoid creating custom elements for form inputs
- browser APIs currently have no way to hook into form submission logic
- if you need styled controls:
- if possible, prioritize styling elements directly with CSS
- otherwise, wrap native
<input>
,<select>
, and<textarea>
elements for styling via Shadow DOM
- if you need styled controls:
- browser APIs currently have no way to hook into form submission logic
- Custom Elements Everywhere / Polymer Summit 2017 (youtube)
- HTML Element Constructors (WHATWG Spec)
- Custom Elements (WHATWG Spec,
W3C Spec)