Skip to main content Accessibility Feedback

Web Component

A way to define reusable custom HTML elements, with built-in styles and interactivity.

Example: a loading icon.

Change my-library to whatever namespace you’d like to use for your custom element, and MyLibrary to whatever namespace you’d like to use for your library.

Web Component element names must include a -, and be registered with JavaScript. The JavaScript class for your library extends the HTMLElement object.

Examples

<my-library></my-library>

The Boilerplate

class MyLibrary extends HTMLElement {

	// The class constructor object
	constructor () {

		// Always call super first in constructor
		super();

		// Creates a shadow root
		this.root = this.attachShadow({mode: 'closed'});

		// Render HTML
		this.root.innerHTML =
			`<style>
				@media (prefers-reduced-motion: no-preference) {

					.loading-ring {
						display: inline-block;
						position: relative;
						width: 5em;
						height: 5em;
					}

					.loading-ring div {
						box-sizing: border-box;
						display: block;
						position: absolute;
						width: 4em;
						height: 4em;
						margin: 0.5em;
						border: 0.5em solid #000000;
						border-radius: 50%;
						animation: spin 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
						border-color: #000000 transparent transparent transparent;
					}

					.loading-ring div:nth-child(1) {
						animation-delay: -0.45s;
					}

					.loading-ring div:nth-child(2) {
						animation-delay: -0.3s;
					}

					.loading-ring div:nth-child(3) {
						animation-delay: -0.15s;
					}

					@keyframes spin {
						0% {
							transform: rotate(0deg);
						}
						100% {
							transform: rotate(360deg);
						}
					}

					/**
					 * Visually hide an element, but leave it available for screen readers
					 * @link https://github.com/h5bp/html5-boilerplate/blob/master/dist/css/main.css
					 * @link http://snook.ca/archives/html_and_css/hiding-content-for-accessibility
					 * @link https://github.com/h5bp/main.css/issues/12#issuecomment-321106995
					 */
					.visually-hidden {
						border: 0;
						clip: rect(0 0 0 0);
						height: 1px;
						overflow: hidden;
						padding: 0;
						position: absolute;
						white-space: nowrap;
						width: 1px;
					}

				}
			</style>
			<span class="visually-hidden" role="status">
				<slot>Loading...</slot>
			</span>
			<div class="loading-ring">
				<div></div>
				<div></div>
				<div></div>
				<div></div>
			</div>`;

	}

	// Runs when the value of an attribute is changed on the component
	attributeChangedCallback (name, oldValue, newValue) {

		// Remove the icon
		let icon = this.root.querySelector('.loading-ring');
		icon.remove();

		// Show a content loaded message
		let notify = this.root.querySelector('[role="status"]');
		notify.textContent = newValue.length ? newValue : 'Content loaded';

	}

	// Create a list of attributes to observe
	static get observedAttributes () {
		return ['loaded'];
	}

}

if ('customElements' in window) {
	customElements.define('my-library', MyLibrary);
}