Thursday, August 1, 2024
HomeProgrammingHTML Net Elements Make Progressive Enhancement And CSS Encapsulation Simpler!

HTML Net Elements Make Progressive Enhancement And CSS Encapsulation Simpler!


I’ve to thank Jeremy Keith and his splendidly insightful article from late final yr that launched me to the idea of HTML Net Elements. This was the “a-ha!” second for me:

Once you wrap some current markup in a customized ingredient after which apply some new behaviour with JavaScript, technically you’re not doing something you couldn’t have finished earlier than with some DOM traversal and occasion dealing with. But it surely’s much less fragile to do it with an internet element. It’s transportable. It obeys the one duty precept. It solely does one factor but it surely does it properly.

Till then, I’d been beneath the false assumption that all net elements rely solely on the presence of JavaScript together with the moderately scary-sounding Shadow DOM. Whereas it’s certainly attainable to writer net elements this manner, there may be one more method. A greater method, maybe? Particularly if you happen to, like me, advocate for progressive enhancement. HTML Net Elements are, in any case, simply HTML.

Whereas it’s exterior the precise scope of what we’re discussing right here, Any Bell has a latest write-up that gives his (glorious) tackle what progressive enhancement means.

Let’s take a look at three particular examples that showcase what I believe are the important thing options of HTML Net Elements — CSS type encapsulation and alternatives for progressive enhancement — with out being compelled to rely upon JavaScript to work out of the field. We’ll most undoubtedly use JavaScript, however the elements should work with out it.

The examples can all be present in my Net UI Boilerplate element library (constructed utilizing Storybook), together with the related supply code in GitHub.

Instance 1: <webui-disclosure>

Storybook render of webui-disclosure Web Component.q
Reside demo

I actually like how Chris Ferdinandi teaches constructing an internet element from scratch, utilizing a disclosure (present/conceal) sample for example. This primary instance extends his demo.

Let’s begin with the first-class citizen, HTML. Net elements permit us to ascertain customized parts with our personal naming, which is the case on this instance with a <webui-disclosure> tag we’re utilizing to carry a <button> designed to indicate/conceal a block of textual content and a <div> that holds the <p> of textual content we need to present and conceal.

<webui-disclosure
  data-bind-escape-key
  data-bind-click-outside
>
  <button
    kind="button"
    class="button button--text"
    data-trigger
    hidden
  >
    Present / Cover
  </button>

  <div data-content>
    <p>Content material to be proven/hidden.</p>
  </div>
</webui-disclosure>

If JavaScript is disabled or doesn’t execute (for any variety of attainable causes), the button is hidden by default — due to the hidden attribute on it— and the content material within the div is solely displayed by default.

Good. That’s a extremely easy instance of progressive enhancement at work. A customer can view the content material with or with out the <button>.

I discussed that this instance extends Chris Ferdinandi’s preliminary demo. The important thing distinction is you could shut the ingredient both by clicking the keyboard’s ESC key or clicking anyplace exterior the ingredient. That’s what the 2 [data-attribute]s on the <webui-disclosure tag are for.

We begin by defining the customized ingredient in order that the browser is aware of what to do with our made-up tag title:

customElements.outline('webui-disclosure', WebUIDisclosure);

Customized parts have to be named with a dashed-ident, resembling <my-pizza> or no matter, however as Jim Neilsen notes, by the use of Scott Jehl, that doesn’t precisely imply that the sprint has to go between two phrases.

I usually choose utilizing TypeScript for writing JavaScript to assist get rid of silly errors and implement some extent of “defensive” programming. However for the sake of simplicity, the construction of the online element’s ES Module appears to be like like this in plain JavaScript:

default class WebUIDisclosure extends HTMLElement {
  constructor() 

  setupA11y() {
    // Add ARIA props/state to button.
  }

  // Deal with constructor() occasion listeners.
  handleEvent(e) {
    // 1. Toggle visibility of content material.
    // 2. Toggle ARIA expanded state on button.
  }
  
  // Deal with occasion listeners which aren't a part of this Net Element.
  connectedCallback() {
    doc.addEventListener('keyup', (e) => {
      // Deal with ESC key.
    });
  
    doc.addEventListener('click on', (e) => {
      // Deal with clicking exterior.
    });
  }

  disconnectedCallback() {
    // Take away occasion listeners.
  }
}

Are you questioning about these occasion listeners? The primary one is outlined within the constructor() operate, whereas the remainder are within the connectedCallback() operate. Hawk Ticehurst explains the rationale rather more eloquently than I can.

This JavaScript isn’t required for the online element to “work” but it surely does sprinkle in some good performance, to not point out accessibility concerns, to assist with the progressive enhancement that enables the <button> to indicate and conceal the content material. For instance, JavaScript injects the suitable aria-expanded and aria-controls attributes enabling those that depend on display readers to know the button’s goal.

That’s the progressive enhancement piece to this instance.

For simplicity, I’ve not written any further CSS for this element. The styling you see is solely inherited from current international scope or element types (e.g., typography and button).

Nonetheless, the following instance does have some further scoped CSS.

Instance 2: <webui-tabs>

That first instance lays out the progressive enhancement advantages of HTML Net Elements. One other profit we get is that CSS types are encapsulated, which is a flowery method of claiming the CSS doesn’t leak out of the element. The types are scoped purely to the online element and people types is not going to battle with different types utilized to the present web page.

Let’s flip to a second instance, this time demonstrating the type encapsulating powers of net elements and how they help progressive enhancement in consumer experiences. We’ll be utilizing a tabbed element for organizing content material in “panels” which might be revealed when a panel’s corresponding tab is clicked — the identical type of factor you’ll discover in lots of element libraries.

Storybook render of the webui-tabs web component.
Reside demo

Beginning with the HTML construction:

<webui-tabs>
  <div data-tablist>
    <a href="#tab1" data-tab>Tab 1</a>
    <a href="#tab2" data-tab>Tab 2</a>
    <a href="#tab3" data-tab>Tab 3</a>
  </div>

  <div id="tab1" data-tabpanel>
    <p>1 - Lorem ipsum dolor sit amet consectetur.</p>
  </div>

  <div id="tab2" data-tabpanel>
    <p>2 - Lorem ipsum dolor sit amet consectetur.</p>
  </div>

  <div id="tab3" data-tabpanel>
    <p>3 - Lorem ipsum dolor sit amet consectetur.</p>
  </div>
</webui-tabs>

You get the thought: three hyperlinks styled as tabs that, when clicked, open a tab panel holding content material. Be aware that every [data-tab] within the tab listing targets an anchor hyperlink matching a tab panel ID, e.g., #tab1, #tab2, and many others.

We’ll take a look at the type encapsulation stuff first since we didn’t go there within the final instance. Let’s say the CSS is organized like this:

webui-tabs {
  [data-tablist] {
    /* Default types with out JavaScript */
  }
  
  [data-tab] {
    /* Default types with out JavaScript */
  }

  [role="tablist"] {
    /* Model position added by JavaScript */
  }
  
  [role="tab"] {
    /* Model position added by JavaScript */
  }
  
  [role="tabpanel"] {
    /* Model position added by JavaScript */
  }
}

See what’s occurring right here? We’ve got two type guidelines — [data-tablist] and [data-tab] — that include the online element’s default types. In different phrases, these types are there no matter whether or not JavaScript masses or not. In the meantime, the opposite three type guidelines are selectors which might be injected into the element so long as JavaScript is enabled and supported. This manner, the final three type guidelines are solely utilized if JavaScript plops the **position** attribute on these parts within the HTML. Proper there, we’re already supplying a contact of progressive enhancement by setting types solely when JavasScript is required.

All these types are totally encapsulated, or scoped, to the <webui-tabs> net element. There isn’t any “leakage” so to talk that may bleed into the types of different net elements, and even to anything on the web page inside the international scope. We are able to even select to forego classnames, advanced selectors, and methodologies like BEM in favour of easy descendent selectors for the element’s kids, permitting us to jot down types extra declaratively on semantic parts.

Rapidly: “Gentle” DOM versus Shadow DOM

For many net tasks, I usually choose to bundle CSS (together with the net element Sass partials) right into a single CSS file in order that the element’s default types can be found within the international scope, even when the JavaScript doesn’t execute.

Nonetheless, it’s attainable to import a stylesheet through JavaScript that’s solely consumed by this net element if JavaScript is offered:

import types from './types.css';

class WebUITabs extends HTMLElement {
  constructor() {
    tremendous();
    this.adoptedStyleSheets = [styles];
  }
}

customElements.outline('webui-tabs', WebUITabs);

Alternatively, we might inject a <type> tag containing the element’s types as an alternative:

class WebUITabs extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' }); // Required for JavaScript entry
    this.shadowRoot.innerHTML = `
      <type> <!-- types go right here --> </type>
      // and many others.
    `;
  }
}
    
customElements.outline('webui-tabs', WebUITabs);

Whichever technique you select, these types are scoped on to the online element, stopping element types from leaking out, however permitting international types to be inherited.

Now contemplate this straightforward instance. Every thing we write in between the element’s opening and shutting tags is taken into account a part of the “Gentle” DOM.

<my-web-component>
  <!-- That is Gentle DOM -->
  <div>
    <p>Some content material... types are inherited from the worldwide scope</p>
  </div>

  ----------- Shadow DOM Boundary -------------
  | <!-- Something injected by JavaScript -->  |
  ---------------------------------------------
</my-web-component>

Dave Rupert has a superb write-up that makes it very easy to see how exterior types are in a position to “pierce” the Shadow DOM and choose a component within the Gentle DOM. Discover how the <button> ingredient that’s written in between the customized ingredient’s tags receives the button selector’s types within the international CSS, whereas the <button> injected through JavaScript is left untouched.

If we need to type the Shadow DOM <button> we’d have to try this with inner types just like the examples above for importing a stylesheet or injecting an inline <type> block.

That doesn’t imply that all CSS type properties are blocked by the Shadow DOM. The truth is, Dave outlines 37 properties that net elements inherit, largely alongside the strains of textual content, listing, and desk formatting.

Progressively improve the tabbed element with JavaScript

Regardless that this second instance is extra about type encapsulation, it’s nonetheless a superb alternative to see the progressive enhancement we get virtually at no cost from net elements. Let’s step into the JavaScript now so we will see how we will help progressive enhancement. The complete code is sort of prolonged, so I’ve abbreviated issues a bit to assist make the factors just a little clearer.

default class WebUITabs extends HTMLElement {
  constructor() {
    tremendous();
    this.tablist = this.querySelector('[data-tablist]');
    this.tabpanels = this.querySelectorAll('[data-tabpanel]');
    this.tabTriggers = this.querySelectorAll('[data-tab]');

    if (
      !this.tablist ||
      this.tabpanels.size === 0 ||
      this.tabTriggers.size === 0
    ) return;
    
    this.createTabs();
    this.tabTriggers.forEach((tabTrigger, index) => {
      tabTrigger.addEventListener('click on', (e) => {
        this.bindClickEvent(e);
      });
      tabTrigger.addEventListener('keydown', (e) => {
        this.bindKeyboardEvent(e, index);
      });
    });
  }

  createTabs() {
    // 1. Cover all tabpanels initially.
    // 2. Add ARIA props/state to tabs & tabpanels.
  }

  bindClickEvent(e) {
    e.preventDefault();
    // Present clicked tab and replace ARIA props/state.
  }
  bindKeyboardEvent(e, index) {
    e.preventDefault();
    // Deal with keyboard ARROW/HOME/END keys.
  }
}

customElements.outline('webui-tabs', WebUITabs);

The JavaScript injects ARIA roles, states, and props to the tabs and content material blocks for display reader customers, in addition to further keyboard bindings so we will navigate between tabs with the keyboard; for instance, the TAB secret’s reserved for accessing the element’s lively tab and any focusable content material contained in the lively tabpanel, and the tabs will be traversed with the ARROW keys. So, if JavaScript fails to load, the default expertise continues to be an accessible one the place the tabs nonetheless anchor hyperlink to their respective panels, and people panels naturally stack vertically, one on high of the opposite.

And if JavaScript is enabled and supported? We get an enhanced expertise, full with up to date accessibility concerns.

Instance 3: <webui-ajax-loader>

Storybook render of webui-ajax-loader web component.
Reside demo

This ultimate instance differs from the earlier two in that it’s fully generated by JavaScript, and makes use of the Shadow DOM. It’s because it is just used to point a “loading” state for Ajax requests, and is due to this fact solely wanted when JavaScript is enabled.

The HTML markup is simply the opening and shutting element tags:

<webui-ajax-loader></webui-ajax-loader>

The simplified JavaScript construction:

default class WebUIAjaxLoader extends HTMLElement {
  constructor() {
    tremendous();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <svg position="img" half="svg">
        <title>loading</title>
        <circle cx="50" cy="50" r="47" />
      </svg>
    `;
  }
}

customElements.outline('webui-ajax-loader',WebUIAjaxLoader);

Discover proper out of the gate that all the things in between the <webui-ajax-loader> tags is injected with JavaScript, which means it’s all within the Shadow DOM, encapsulated from different scripts and types circuitously bundled with the element.

But in addition discover the half attribute that’s set on the <svg> ingredient. Right here’s the place we’ll zoom in:

<svg position="img" half="svg">
  <!-- and many others. -->
</svg>

That’s one more method now we have to type the customized ingredient: named components. Now we will type that SVG from exterior of the template literal we used to ascertain the ingredient. There’s a ::half pseudo-selector to make that occur:

webui-ajax-loader::half(svg) {
  // Shadow DOM types for the SVG...
}

And right here’s one thing cool: that selector can entry CSS customized properties, whether or not they’re scoped globally or domestically to the ingredient.

webui-ajax-loader {
  --fill: orangered;
}

webui-ajax-loader::half(svg) {
  fill: var(--fill);
}

So far as progressive enhancement goes, JavaScript provides the entire HTML. Meaning the loader is barely rendered if JavaScript is enabled and supported. And when it’s, the SVG is added, full with an accessible title and all.

Wrapping up

That’s it for the examples! What I hope is that you simply now have the identical type of epiphany that I had when studying Jeremy Keith’s publish: HTML Net Elements are an HTML-first function.

In fact, JavaScript does play an enormous position, however solely as huge as wanted. Want extra encapsulation? Wish to sprinkle in some UX goodness when a customer’s browser helps it? That’s what JavaScript is for and that’s what makes HTML Net Elements such an amazing addition to the online platform — they depend on vanilla net languages to do what they have been designed to do all alongside, and with out leaning too closely on one or the opposite.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments