bp

8 Nights of Web Components Tips: 5783

Hannukah menorah in glass case, eight candles and shamash are lit.

Back for another year, it's 8 days of Web Components tips!

Candle 1: Hiding Unwanted Content

On the 1st night, let's start at the end. When the Maccabees liberated the Holy Temple in Jerusalem from Seleucid Greek invaders, they refused to light the Menorah with the impure oil they found left over.

To prevent your element from displaying unwanted content, use the ::slotted pseudo element and the :not pseudo class in your CSS.

::slotted(shemen-zayit:not([type="zach"])) {
  display: none !important;
}

Candle 2: CSS Shadow Parts

CSS shadow parts let users of your element style parts of it from the outside, but did you know that just like the class attribute, part can have multiple part names, & like the class selector (.), the ::part() pseudo element can take a space-separated list?

That means you can create multiple overlapping sets of parts, to let your users choose how mehudar (beautiful) their elements should be.

render() {
  return html`
    <ul>${Array.from({ length: 8 }, (_, i) => html`
      <li part="candle ${this.night < i ? '' : 'un'}lit"></li>`)}
      <li part="candle shamash lit"></li>
    </ul>
  `;
}
web-menorah::part(candle lit)::after {
  content: '🕯️';
}

web-menorah::part(candle unlit)::after {
  content: '🕳️';
}

web-menorah::part(candle shamash)::after {
  translate: 0 8px;
}

Live demo

Candle 3: Form-Associated Custom Elements

Form-Associated Custom Elements can participate in the lifecycle of HTML form elements. Set the static formAssociated boolean flag on the element class and call attachInternals() to hook into the browser's accessibility tree. Create custom form controls that work like native ones!

<form>
  <fieldset>
    <legend>Create Your Own Custom Controls</legend>

    <label for="menorah">Which Night?</label>
    <web-menorah id="menorah" name="night" night="3"></web-menorah>

    <label for="location">Light Outside?</label>
    <custom-switch id="location" checked></custom-switch>
  </fieldset>

  <custom-button>Light!</custom-button>
</form>

Read more in my post on FACE.

Candle 4: Private State

Want to style your elements based on their state, but don't want to create new public APIs or new HTML attributes? Use Lit's classMap directive to set a class on a shadow container based on the private state.

Not using Lit? No problem, classMap is a performance optimization but you could do the same thing imperatively via the DOM or using your component library's APIs.

import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';

import { ApolloQueryController } from '@apollo-elements/core';

import { MyQuery } from './my.query.graphql';

export class StatefulElement extends LitElement {
  #query = new ApolloQueryController(this, MyQuery);
  render() {
    const { loading, error } = this.#query;
    return html`
      <div id="container"
           class="${classMap({ loading, error })}">
        <p>${this.#query.data}</p>
      </div>`;
  }
}
#container {
  display: contents;
}

.loading {
  opacity: 0.5;
  background: grey;
}

.error {
  color: red;
}

Candle 5: Noscript

Not every end-user of your web component can or wants to enable javascript. Make sure to add <noscript> examples to your documentation. Even if your custom element does fancy footwork like syntax-highlighting non-js script tags by reading textContent, you can still gracefully degrade for noscript users.

<code-block>
  <script type="text/snippet-js">
    console.log("With JS on, this gets highlighted but not run");
  </script>
</code-block>

<style>
  code-block:not(:defined),
  code-block:not(:defined) > script {
    display: block;
  }
</style>

<noscript>
  <style>
    code-block, code-block > script {
      display: block;
    }
  </style>
</noscript>

Candle 6: Bare Module Specifiers

The Maccabees fought for the freedom to control their own destiny.

If your web component imports dependencies (like a base class or utilities) from an NPM module, use "bare" module specifiers in your sources. This gives your users maximum flexibility, letting them bundle, serve via module-transforming CDN (like JSPM or UNPKG) or use import maps to resolve the import specifiers to URLs.

// ✅ DO
import { LitElement } from 'lit';

// ❌ DON'T
import { LitElement } from '../../node_modules/lit/index.js';

Candle 7: Interop, Part 1

The Seleucid king Antiochus tried to impose a uniform Hellenistic culture on the Jews, but your organization doesn't need to suffer a front-end monoculture. Web components work everywhere HTML does, so use them in your CMS' templates, in templates, in templates, in templates, in templates, in in in in your in your your your your your your your Angular forms, in your Vue or Preact apps, or on performant and accessible web pages.

Write once, use everywhere.

Candle 8: Interop, Part 2

The last night of Hannukah is called "This is Hannukah", since it encapsulates the entirety of the holiday.

The big idea of web components is interoperability, meaning you can use components with each other, mixing & matching, no matter how they were written. The common interface of HTML, CSS, & DOM events/props ties them together.

Use lit components with stencil, or hybrids with FAST, and vice versa.