<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 →

Container Queries with the <watched-box> Custom Element

Heydon Pickering has created <watched-box>:

I wanted a simple, declarative container queries solution, and here it is:

  • ❤ Custom Element + ResizeObserver
  • 🥣 Use and mix together any CSS length units
  • 🖼 Orientation supported
  • 🧚‍♀️ ≅1.5KB minified

Once imported you can use it as follows:

<watched-box widthBreaks="70ch, 900px" heightBreaks="50vh, 60em">
  <!-- HTML and text stuff here -->
</watched-box>

<watched-box> will then automatically add the proper classes:

  • Less than or equal to the supplied width: w-lte-[the width]
  • Greater than the supplied width: w-gt-[the width]
  • Less than or equal to the supplied height: h-lte-[the height]
  • Greater than the supplied height: h-gt-[the height]

<watched-box>

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 →