Integrate Custom Elements into Forms with ElementInternals

Over at CSS-Tricks, Caleb Williams digs into ElementInternals

The ElementInternals standard is a very exciting set of features with a very unassuming name. Among the features internals adds are the ability to participate in forms and an API around accessibility controls.

As you can see in the demo below his <rad-input> does exactly that.

See the Pen
Rad input with validation
by Caleb Williams (@calebdwilliams)
on CodePen.

Creating Custom Form Controls with ElementInternals

🤨 New to Web Components? John Rhea recently wrote two nice articles for CSS-Tricks covering the basics:

<tab-container> Custom Element — An Accessible Tab Container Element with Keyboard Support

Built by GitHub is <tab-container>, a Custom Element that defines an accessible tab container element with keyboard support and that also follows the ARIA best practices guide on tabs.

Upon importing the package it will register the Custom Element, so you can use it immediately:

<tab-container>
  <div role="tablist">
    <button type="button" role="tab" aria-selected="true">Tab one</button>
    <button type="button" role="tab" tabindex="-1">Tab two</button>
    <button type="button" role="tab" tabindex="-1">Tab three</button>
  </div>
  <div role="tabpanel">
    Panel 1
  </div>
  <div role="tabpanel" hidden>
    Panel 2
  </div>
  <div role="tabpanel" hidden>
    Panel 3
  </div>
</tab-container>

Installation possible per NPM:

npm install @github/tab-container-element

… or import it directly off of Skypack

import "https://cdn.skypack.dev/@github/tab-container-element";

<tab-container> Source →
<tab-container> Demo →

Options for styling Web Components

Nolan Lawson, author of emoji-picker-element, a web component you can use to provide an emoji-picker:

What wasn’t obvious to me, though, was how to allow users to style it. What if they wanted a different background color? What if they wanted the emoji to be bigger? What if they wanted a different font for the input field?

This led me to researching all the different ways that a standalone web component can expose a styling API. In this post, I’ll go over each strategy, as well as its strengths and weaknesses from my perspective.

Options for styling web components →

The Case for Web Components

With the release of “Edgium”, all major browsers now support Web Components.

Perfect time to link to this by Viljami Salminen in which he makes the case for Web Components:

We couldn’t just choose [a framework] because the different tech stacks were picked by different teams (or even different sub-companies) for different reasons and we would have never succeeded if we would have tried to force everyone to use a specific technology like React.

Instead, we had the urge to create a technology-agnostic instead of technology-specific system. A system that is based on Web Standards and would survive the births and deaths of JavaScript frameworks.

I’m very curious to see how Stencil wil fare from all this.

Why We Use Web Components →

💡 Over at CSS Tricks you can find a solid introduction to Web Components, along with this recent one on the various current (and future) styling options for Web Components.

When in doubt, trust the browser

Presentation by Pieter Beulque, as given at the most recent FronteersBE meetup, in which he builds a Tweet Embed using Web Components’ Custom Elements.

As a front-end developer, you’re always confronted with making choices. In this talk I cover the benefits you get for free when considering W3C standards or standard browser API’s instead of reaching for the latest framework or library.

💡 To format the time, he uses the previously covered Intl.RelativeTimeFormat API, which will be part of ES2020.

When in doubt, trust the browser →

More performant YouTube embeds with <lite-youtube-embed>

By Paul Irish:

Provide videos with a supercharged focus on visual performance. This custom element renders just like the real thing but approximately 224X faster.

Installation per NPM:

npm install lite-youtube-embed

Usage:

<!-- Include the stylesheet, this could be direct from the package or bundled -->
<link rel="stylesheet" href="node_modules/lite-youtube-embed/src/lite-yt-embed.css" />

<!-- Include the custom element script -->
<script src="node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>

<!-- Use the element. You may define uses before the scripts are parsed and executed. -->
<lite-youtube videoid="ogfYd705cRs"></lite-youtube>

Lite YouTube Embed →

Unicode Patterns with <css-doodle />

<css-doodle /> is a web component for drawing patterns with CSS.

The component will generate a grid of divs by the rules (plain CSS) inside it. You can easily manipulate those cells using CSS to come up with a graphic pattern or an animated graph. The limit is the limit of CSS itself.

Combine <css-doodle /> with generated content and Unicode characters, and you can create nice decorative patterns as shown above.

<css-doodle>
  :doodle {
    @grid: 21 / 35em 20em;
    overflow: hidden;
  }

  --c: @pick(
    #11CBD7, #C6F1E7, #FA4659
  );

  :after {
    content: '\@hex(@rand(0x1401, 0x141b))';
    font-size: 2em; 
    background: linear-gradient(
      @rand(360deg),
      transparent 50%, var(--c) 50%
    );
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    -webkit-text-stroke: var(--c);
    -webkit-text-stroke-width: .5px;
    color: transparent;
  } 
</css-doodle>

Unicode Patterns →
Unicode Patterns demos →
<css-doodle />

Stencil: A Compiler for Web Components

Stencil is a compiler that generates Web Components (more specifically, Custom Elements). Stencil combines the best concepts of the most popular frameworks into a simple build-time tool.

Stencil takes features such as

  • Virtual DOM
  • Async rendering (inspired by React Fiber)
  • Reactive data-binding
  • TypeScript
  • JSX

and then generates standards-based Web Components with these features baked in.

Stencil will be used in Ionic 4, making its component usable within Vue or React.

The magical, reusable web component generator →

Rendering Markdown using Custom Elements v1

Inspired upon a v0 implementation, I’ve recreated a Markdown renderer in Custom Elements v1. The result is <custom-markdown>.

The code itself is pretty straightforward: other than some (contained) styling the custom element uses showdown to convert the Markdown to HTML. This conversion is triggered in the connectedCallback().

class Markdown extends HTMLElement {
  
  constructor() {
    super();
    const shadowRoot = this.attachShadow({mode: 'closed'});
    shadowRoot.innerHTML = `
      <style>
        :host { display: block; font-family: monospace; }
      </style>
      <div class="output"></div>
    `;
    this.output = shadowRoot.querySelector('.output');
    this.converter = new showdown.Converter();
  }

  connectedCallback() {
    this.output.innerHTML = this.converter.makeHtml(this.innerHTML);
  }
  
};

customElements.define('custom-markdown', Markdown);

Here’s a working example (if your browser supports it):

See the Pen Custom Elements v1: Render Markdown with <custom-markdown> by Bramus (@bramus) on CodePen.

Web Components: Introducing Custom Elements

One of the new features in Safari Technology Preview 18 is Custom Elements v1 (Chrome/Opera already support for it):

To define a custom element, simply invoke customElements.define with a new local name of the element and a subclass of HTMLElement.

Example Code:

class CustomProgressBar extends HTMLElement {
  constructor() {
      super();
      const shadowRoot = this.attachShadow({mode: 'closed'});
      shadowRoot.innerHTML = `
          <style>
              :host { display: inline-block; width: 5rem; height: 1rem; }
              .progress { display: inline-block; position: relative; border: solid 1px #000; padding: 1px; width: 100%; height: 100%; }
              .progress > .bar { background: #9cf; height: 100%; }
              .progress > .label { position: absolute; top: 0; left: 0; width: 100%;
                  text-align: center; font-size: 0.8rem; line-height: 1.1rem; }
          </style>
          <div class="progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
              <div class="bar" style="width: 0px;"></div>
              <div class="label">0%</div>
          </div>
      `;
      this._progressElement = shadowRoot.querySelector('.progress');
      this._label = shadowRoot.querySelector('.label');
      this._bar = shadowRoot.querySelector('.bar');
  }

  get progress() { return this._progressElement.getAttribute('aria-valuenow'); }
  set progress(newPercentage) {
      this._progressElement.setAttribute('aria-valuenow', newPercentage);
      this._label.textContent = newPercentage + '%';
      this._bar.style.width = newPercentage + '%';
  }
};
customElements.define('custom-progress-bar', CustomProgressBar);

Once defined you can then use it as follows in your HTML:

<custom-progress-bar"></custom-progress-bar>

It is also possible to create instances using JavaScript, and to define the allowed attributes for custom elements (and listen for changes on them).

Introducing Custom Elements →
Custom Element Demo →