Creating Custom HTML

(Photo by Kenny Louie / Flickr) An exciting feature of the HTML specification is creating custom HTML elements. These allow you to create your own HTML elements along with their own JavaScript API. This can be useful when building interfaces with reused...

The post Creating Custom HTML appeared first on Treehouse Blog.

Shadow Roots and Inheritance

There is a helluva gotcha with styling a <details> element, as documented here by Kitty Guiraudel. It’s obscure enough that you might never run into it, but if you do, I could see it being very confusing (it would confuse me, at least).

Perhaps you’re aware of the shadow DOM? It’s talked about a lot in terms of web components and comes up when thinking in terms of <svg> and <use>. But <details> has a shadow DOM too:

<details>
  #shadow-root (user-agent)
  <slot name="user-agent-custom-assign-slot" id="details-summary">
    <!-- <summary> reveal -->
  </slot>
  <slot name="user-agent-default-slot" id="details-content">
    <!-- <p> reveal -->
  </slot>

  <summary>System Requirements</summary>
  <p>
    Requires a computer running an operating system. The computer must have some
    memory and ideally some kind of long-term storage. An input device as well
    as some form of output device is recommended.
  </p>
</details>

As Amelia explains, the <summary> is inserted in the first shadow root slot, while the rest of the content (called “light DOM”, or the <p> tag in our case) is inserted in the second slot.

The thing is, none of these slots or the shadow root are matched by the universal selector *, which only matches elements from the light DOM. 

So the <slot> is kind of “in the way” there. That <p> is actually a child of the <slot>, in the end. It’s extra weird, because a selector like details > p will still select it just fine. Presumably, that selector gets resolved in the light DOM and then continues to work after it gets slotted in.

But if you tell a property to inherit, things break down. If you did something like…

<div>
  <p></p>
</div>
div {
  border-radius: 8px;
}
div p {
  border-radius: inherit;
}

…that <p> is going to have an 8px border radius.

But if you do…

<details>
  <summary>Summary</summary>
  <p>Lorem ipsum...</p>
</details>
details {
  border-radius: 8px;
}
details p {
  border-radius: inherit;
}

That <p> is going to be square as a square doorknob. I guess that’s either because you can’t force inheritance through the shadow DOM, or the inherit only happens from the parent which is a <slot>? Whatever the case, it doesn’t work.

Direct Link to ArticlePermalink


The post Shadow Roots and Inheritance appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Platform News: Using :focus-visible, BBC’s New Typeface, Declarative Shadow DOMs, A11Y and Placeholders

There’s a whole lot of accessibility in this week’s news, from the nuances of using :focus-visible and input placeholders, to accessible typefaces and a Safari bug with :display: contents. Plus, a snippet for a bare-bones web component that supports style encapsulation.

Now may be a good time to start using :focus-visible

The CSS :focus-visible pseudo-class replaces :focus as the new way to create custom focus indicators for keyboard users. Chrome recently switched from :focus to :focus-visible in the user agent stylesheet and, as a result of that change, the default focus ring is no longer shown when the user clicks or taps a button.

When switching from :focus to :focus-visible, consider backwards compatibility. Your keyboard focus indicators should be clearly visible in all browsers, not just the ones that support :focus-visible. If you only style :focus-visible, non-supporting browsers will show the default focus ring which, depending on your design, “may not be sufficiently clear or visible at all.”

button {
  background: white;
}

button:focus-visible {
  outline: none;
  background: #ffdd00; /* gold */
}

A good way to start using :focus-visible today is to define the focus styles in a :focus rule and then immediately undo these same styles in a :focus:not(:focus-visible) rule. This is admittedly not the most elegant and intuitive pattern, but it works well in all browsers:

  • Browsers that don’t support :focus-visible use the focus styles defined in the :focus rule and ignore the second style rule completely (because :focus-visible is unknown to them).
  • In browsers that do support :focus-visible, the second style rule reverts the focus styles defined in the :focus rule if the :focus-visible state isn’t active as well. In other words, the focus styles defined in the :focus rule are only in effect when :focus-visible is also active.
button:focus {
  outline: none;
  background: #ffdd00; /* gold */
}

button:focus:not(:focus-visible) {
  background: white; /* undo gold */
}

The BBC created a more accessible typeface

The BBC created their own custom typeface called Reith (named after the BBC’s founder Sir John Reith). Their goal was to create a font that supports multiple languages and is easier to read, especially on small devices. The font was tested with mixed-ability user groups (dyslexia and vision impairment) and across different screen sizes.

We [the BBC] were using Helvetica or Arial. We also had Gill Sans as the corporate typeface. These typefaces were designed a hundred years ago for the printed page [and] don’t perform well on today’s modern digital screens.

Reith Sans can bee seen in use on BBC Sport

Note: If you’d like to inspect Reith Sans and Reith Serif in Wakamai Fondue, you can quickly access the URLs of the WOFF2 files in the “All fonts on page” section of the Fonts pane in Firefox’s DOM inspector on BBC’s website.

display: contents is still not accessible in Safari

The CSS display: contents value has been supported in browsers since 2018. An element with this value “does not generate any boxes” and is effectively replaced by its children. This is especially useful in flex and grid layouts, where the contents value can be used to “promote” more deeply nested elements to flex/grid items while retaining a semantic document structure.

Source: Manuel Rego Casasnovas

Unfortunately, this feature originally shipped with an implementation bug that removed the element from the browser’s accessibility tree. For example, applying display: contents to a <ul> element resulted in that element no longer mentioned by screen readers. Since then, this bug has been fixed in Firefox and Chrome (in the latest version).

View on CodePen

In Chrome and Firefox, the screen reader informs the user that the “Main, navigation” contains a “list, 2 items.” In Safari, the latter part is missing because the <ul> and <li> elements are not present in the accessibility tree. Until Apple fixes this bug in Safari, be careful when using the contents value on semantic elements and test in screen readers to confirm that your pages are accessible in Safari as well.

Set opacity when overriding the color of placeholder text

Accessibility experts recommend avoiding placeholders if possible because they can be confused for pre-populated text and disappear when the user starts entering a value. However, many websites (including Wikipedia and GOV.UK) use placeholders in simple web forms that contain only a single input field, such as a search field.

The subscription form for the CSS-Tricks newsletter uses a placeholder in the email field

Placeholders can be styled via the widely supported ::placeholder pseudo-element. If your design calls for a custom color for placeholder text, make sure to specify both color and opacity. The latter is needed for Firefox, which applies opacity: 0.54 to ::placeholder by default. If you don’t override this value, your placeholder text may have insufficient contrast in Firefox.

.search-field::placeholder {
  color: #727272;
  opacity: 1; /* needed for Firefox */
}
The placeholder text on eBay’s website is lighter in Firefox and doesn’t meet the minimum contrast requirement of 4.5:1

Declarative shadow DOM could help popularize style encapsulation

One of the key features of shadow DOM is style encapsulation, wherein the outer page’s style rules don’t match elements inside the shadow tree, and vice versa. In order to use this feature, you need to attach a shadow DOM tree to an element (usually a custom element, like <my-element>) and copy the element’s template (usually from a <template> element in the DOM) to the element’s newly created shadow DOM.

These steps can only be performed in JavaScript. If you’re only interested in style encapsulation and don’t need any dynamic functionality for your element, here is the minimum amount of JavaScript required to create a custom element with a shadow DOM:

customElements.define(
  "my-element",
  class extends HTMLElement {
    constructor() {
      super();

      // find <template id="my-template"> in the DOM
      let template = document.getElementById("my-template");

      // make a copy of the template contents…
      let content = template.content.cloneNode(true);

      // …and inject it into <my-element>’s shadow DOM
      this.attachShadow({ mode: "open" }).appendChild(content);
    }
  }
);

For an example of style encapsulation, see Miriam Suzanne’s <media-object> element on CodePen. The scoped styles are located in the <template> element in the HTML pane. Notice how this CSS code can use simple selectors, such as article, that only match elements inside <media-object>’s shadow DOM.

JavaScript may soon no longer be required to create this type of style encapsulation in modern browsers. Earlier this week, Chrome became the first browser to ship Google’s Declarative Shadow DOM proposal. If it becomes a standard, this feature will also make it possible to use Shadow DOM in combination with server-side rendering.


The post Platform News: Using :focus-visible, BBC’s New Typeface, Declarative Shadow DOMs, A11Y and Placeholders appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Query Languages That Can Handle ShadowDOM

XPath is a query language that is widely used by developers and QA automation engineers. Very often, XPath becomes the main engine for querying DOM nodes inside Selenium projects. Its ability to walk up or down to apply different filters and functions makes it a solid choice.

What Has Changed?

UIs are becoming increasingly more complicated. This can make for a real headache for developers who have to design their applications to be easily supportable and scalable. 

Weekly Platform News: HTML Loading Attribute, the Main ARIA Specifications, and Moving from iFrame to Shadow DOM

In this week's roundup of platform news, Chrome introduces a new attribute for loading, accessibility specifications for web developers, and the BBC moves visualizations to the Shadow DOM.

Chrome ships the loading attribute

The HTML loading attribute for lazy-loading images and iframes is now supported in Chrome. You can add loading="lazy" to defer the loading of images and iframes that are below the viewport until the user scrolls near them.

Google suggests either treating this feature as a progressive enhancement or using it on top of your existing JavaScript-based lazy-loading solution.

This feature has not yet been added to the HTML Standard (but there is an open pull request), and multiple links to Google’s documentation are listed on its Chrome Status page.

(via web.dev)


Overview of ARIA specifications

The main accessibility specifications for web developers:

Name Description
ARIA in HTML Defines which ARIA role, state, and property attributes are allowed on which HTML elements (the implicit ARIA semantics are defined here)
Using ARIA Provides practical advice on how to use ARIA in HTML, with an emphasis on dynamic content and advanced UI controls (the “five rules of ARIA use” are defined here)
ARIA (Accessible Rich Internet Applications) Defines the ARIA roles, states, and properties
ARIA Authoring Practices Provides general guidelines on how to use ARIA to create accessible apps (includes ARIA implementation patterns for common widgets)
HTML Accessibility API Mappings Defines how browsers map HTML elements and attributes to the operating system’s accessibility APIs
WCAG (Web Content Accessibility Guidelines) Provides guidelines for making web content more accessible (success criteria for WCAG conformance are defined here)

Related: “Contributing to the ARIA Authoring Practices Guide" by Simon Pieters and Valerie Young


Shadow DOM on the BBC website

The BBC has moved from <iframe> to Shadow DOM for the embedded interactive visualizations on its website. This has resulted in significant improvements in load performance (“more than 25% faster”).

The available Shadow DOM polyfills didn’t reliably prevent styles from leaking across the Shadow DOM boundary, so they decided to instead fall back to <iframe> in browsers that don’t support Shadow DOM.

Shadow DOM [...] can deliver content in a similar way to iframes in terms of encapsulation but without the negative overheads [...] We want encapsulation of an element whose content will appear seamlessly as part of the page. Shadow DOM gives us that without any need for a custom element.

One major drawback of this new approach is that CSS media queries can no longer be used to conditionally apply styles based on the content’s width (since the content no longer loads in a separate, embedded document).

With iframes, media queries would give us the width of our content; with Shadow DOM, media queries give us the width of the device itself. This is a huge challenge for us. We now have no way of knowing how big our content is when it’s served.

(via Toby Cox)


In other news...

  • The next version of Chrome will introduce the Largest Contentful Paint performance metric; this new metric is a more accurate replacement for First Meaningful Paint, and it measures when the largest element is rendered in the viewport (usually, the largest image or paragraph of text) (via Phil Walton)
  • Microsoft has created a prototype of a new tool for viewing a web page’s DOM in 3D; this tool is now experimentally available in the preview version of Edge (via Edge DevTools)
  • Tracking prevention has been enabled by default in the preview versions of Edge; it is set to balanced by default, which “blocks malicious trackers and some third-party trackers” (via Techdows)

Read more news in my new, weekly Sunday issue. Visit webplatform.news for more information.

The post Weekly Platform News: HTML Loading Attribute, the Main ARIA Specifications, and Moving from iFrame to Shadow DOM appeared first on CSS-Tricks.

Making Web Components for Different Contexts

This article isn’t about how to build web components. Caleb Williams already wrote a comprehensive guide about that recently. Let’s talk about how to work with them, what to consider when making them, and how to embrace them in your projects.

If you are new to web components, Caleb’s guide is a great read, but here are more resources that will get you up to speed:

Since web components are now widely supported (thanks to the hard work of many people behind the scenes) — and considering the imminent switch that Edge will make to the chromium platform — people are now thinking about web components as "native" and a platform-compliant way to build reusable UI components to keep consistency across design systems and web projects, while using the power of the Shadow DOM to encapsulate style and logics inside the component itself.

Well, this can be true and false at the same time. But first, let’s get acquainted with the Abstraction Layers Triangle.

The Abstraction Layers Triangle

Technically, we should consider web components as an extension of our favorite markup language, HTML (yep!). The Web Components API allows us to create custom HTML elements (e.g. <foo-bar>) that don’t exist in HTML.

We are told web components are basically new HTML elements, so we should consider them as part of the HTML specifications and, consequently, we should follow its paradigms, core concepts, and utilization. If we assume all of these points, we will figure out that our components will live among the lowest levels of the web platform stack, alongside HTML, CSS, and JavaScript.

Frameworks and libraries like React, Vue, Angular, SvelteJS work on their abstraction level, right above other tools that live in a sort of "middle earth," like StencilJs, Hybrids and Lit. Under these layers of abstraction, we can find our basic web technologies… and vanilla web components. We can represent this concept with an ALT (A>bstraction L Triangle) diagram:

The higher we go, the more abstraction things get.

Why is this important? Well, it helps us visualize the various layers that exist on top of native components and understand the context they are used so that they can be built for an intended context. What's context? That's where we're headed.

Same technology, different contexts

The Shadow DOM is a key factor in the Web Components API. It allows us to bundle JavaScript and CSS inside a custom element to both prevent external interferences and style manipulations, unless we expressly allow it. There are indeed some approaches that developers can follow to allow external CSS to leak into the shadow root and into a component, including custom properties and the ::part and ::theme pseudo-elements, which is something Monica Dinculescu) has covered so well.

There is also another thing to consider: the context we are working with. After three years of building web components personally, I can identify two contexts: the private context (like a design system) and the standard context (like plain HTML, CSS, and JavaScript without custom styles).

Before designing components, we need to know how they will be used, so determining the type of context is a key to all of this. The most easy way is targeting only one context, but with a small CSS trick. we can build our components for both of them.

Let’s look at the differences between the two contexts before we get into that.

Private context

A private context is a closed ecosystem that provides components with their own style that must be used as-is. So, if we are building a component library that comes from specific styling guidelines or a design system, each component will reflect custom styles so there’s no need to code them up each time they’re needed.

That can be true also with JavaScript logic. For example, we can attach a closed shadow root that prevent others to accidentally pierce the shadow boundary with a querySelector. As a a result, we can simply pick and use all any component, avoiding issues like style inconsistencies and CSS collisions. As the author, you can also get to define a set of CSS custom properties (or ::parts) that can be used to style a component for a specific use case, but this is not the focus point of a design system.

Here’s an example of a web component component in a private context. It has all of the styles and logic contained inside its shadow-root and and can simply be dropped into any page.

See the Pen
Closed Context Web Component
by Mattia Astorino (@equinusocio)
on CodePen.

This example and the one to follow are for demonstration purposes and not intended for production use because they do not make considerations for key situations, like accessibility and other optimizations.

Components in a private context can be rarely used outside of that context. For example, if we try to take an element from a design system (which has its own enforced styles), we’re unable to simply add it to a project and expect to customize it. You know how Bootstrap can be themed and customized to your liking? This is pretty much the opposite of that. These components are made to live inside their context and their context only.

Standard context

A standard context may be the most complex type of component, not only because the environment can range anywhere from a full-fledged framework (like Vue and React) to plain vanilla HTML, but also because everyone should be able to use that component like any other element.

For example, when adding a component publicly, say to npm, those who use it will expect to be able to customize it, at least to some degree.

Do you know of any HTML element that comes with its own presentational style? The answer should be no because, well, elements must be explicitly styled with CSS. This is also true for web components made in a standard context. A single web component should be customizable by adding classes an attributes, or other methods.

Here’s the same <todo-list> element that we saw in the closed context example, but designed for a standard context. It works as-is, without any presentational styles inside its shadow root. In fact, it only contains the required logic and baseline CSS to make sure it functions. Otherwise, it’s completely customizable like any standard HTML element, like a div.

See the Pen
Standard Context Web Component
by Mattia Astorino (@equinusocio)
on CodePen.

Both of the examples we’ve looked at for each context are made with the same component. The difference is that the component in a standard context an be customized and selected with external CSS.

Web components and composition

OK, so working with web components is really the same as working with plain HTML, though as we’ve seen, it’s important to follow the paradigms and principles of the given content. Well, thing we need to be mindful of is the web component composition.

As explained by Google Web Fundamentals:

Composition is one of the least understood features of shadow DOM, but it's arguably the most important.

In our world of web development, composition is how we construct apps, declaratively out of HTML. Different building blocks (<div>s, <header>s, <form>s, <input>s) come together to form apps. Some of these tags even work with each other. Composition is why native elements like <select>, <details>, <form>, and <video> are so flexible. Each of those tags accepts certain HTML as children and does something special with them. For example, <select> knows how to render option> and <optgroup> into dropdown and multi-select widgets. The <details> element renders <summary> as an expandable arrow. Even <video> knows how to deal with certain children: <source> elements don't get rendered, but they do affect the video's behavior. What magic!

Composition is what we normally do when we work with HTML. Since web components are merely HTML elements with a DOM reference — and not logical containers — we should rely on composition to build our components and any sub-components. If you think about the ul and and select you will notice that you declaratively compose these elements to get the final output and you have specific attributes to be used with the main component (e.g. [readonly]) or the sub-component (e.g. [selected]). This is true also for web components, and if you are building a custom list, consider to build the main component (<custom-list>) and the child one (<custom-li>). Using the [slot] element, you can define where children elements will be placed and also placeholder content that will be shown when no children are passed through.

Web components and accessibility

Another thing to consider is that "small" topic we call accessibility. Since we are creating completely new HTML elements, we need to consider the accessibility of our elements in order to provide a basic semantic role, any keyboard navigation as well as the user’s operating system preferences, like the reduce motion and high contrast settings.

I strongly suggest the following resources as reference for building accessible and inclusive components, how to define semantic markup, and how to implement a basic keyboard navigation.

Conclusion

Web components are an emerging technology in web development and, as such, there really aren’t any clearly defined best practices to guide us as far as building them for their intended or maximized use. When you find yourself working with them, perhaps the single thing you can take away from this post is to consider whether they are intended for a closed context or a standard context, then ask yourself WHI:

  • Who will use this component?
  • How much flexibility should that person have to customize it?
  • Is this component for everyone or for a specific audience?

The post Making Web Components for Different Contexts appeared first on CSS-Tricks.

Encapsulating Style and Structure with Shadow DOM

This is part four of a five-part series discussing the Web Components specifications. In part one, we took a 10,000-foot view of the specifications and what they do. In part two, we set out to build a custom modal dialog and created the HTML template for what would evolve into our very own custom HTML element in part three.

Article Series:

  1. An Introduction to Web Components
  2. Crafting Reusable HTML Templates
  3. Creating a Custom Element from Scratch
  4. Encapsulating Style and Structure with Shadow DOM (This post)
  5. Advanced Tooling for Web Components

If you haven’t read those articles, you would be advised to do so now before proceeding in this article as this will continue to build upon the work we’ve done there.

When we last looked at our dialog component, it had a specific shape, structure and behaviors, however it relied heavily on the outside DOM and required that the consumers of our element would need to understand it’s general shape and structure, not to mention authoring all of their own styles (which would eventually modify the document’s global styles). And because our dialog relied on the contents of a template element with an id of "one-dialog", each document could only have one instance of our modal.

The current limitations of our dialog component aren’t necessarily bad. Consumers who have an intimate knowledge of the dialog’s inner workings can easily consume and use the dialog by creating their own <template> element and defining the content and styles they wish to use (even relying on global styles defined elsewhere). However, we might want to provide more specific design and structural constraints on our element to accommodate best practices, so in this article, we will be incorporating the shadow DOM to our element.

What is the shadow DOM?

In our introduction article, we said that the shadow DOM was "capable of isolating CSS and JavaScript, almost like an <iframe>." Like an <iframe>, selectors and styles inside of a shadow DOM node don’t leak outside of the shadow root and styles from outside the shadow root don’t leak in. There are a few exceptions that inherit from the parent document, like font family and document font sizes (e.g. rem) that can be overridden internally.

Unlike an <iframe>, however, all shadow roots still exist in the same document so that all code can be written inside a given context but not worry about conflicts with other styles or selectors.

Adding the shadow DOM to our dialog

To add a shadow root (the base node/document fragment of the shadow tree), we need to call our element’s attachShadow method:

class OneDialog extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.close = this.close.bind(this);
  }
}

By calling attachShadow with mode: 'open', we are telling our element to save a reference to the shadow root on the element.shadowRoot property. attachShadow always returns a reference to the shadow root, but here we don’t need to do anything with that.

If we had called the method with mode: 'closed', no reference would have been stored on the element and we would have to create our own means of storage and retrieval using a WeakMap or Object, setting the node itself as the key and the shadow root as the value.

const shadowRoots = new WeakMap();

class ClosedRoot extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'closed' });
    shadowRoots.set(this, shadowRoot);
  }

  connectedCallback() {
    const shadowRoot = shadowRoots.get(this);
    shadowRoot.innerHTML = `<h1>Hello from a closed shadow root!</h1>`;
  }
}

We could also save a reference to the shadow root on our element itself, using a Symbol or other key to try to make the shadow root private.

In general, the closed mode for shadow roots exists for native elements that use the shadow DOM in their implementation (like <audio> or <video>). Further, for unit testing our elements, we might not have access to the shadowRoots object, making it unable for us to target changes inside our element depending on how our library is architected.

There might be some legitimate use cases for user-land closed shadow roots, but they are few and far between, so we’ll stick with the open shadow root for our dialog.

After implementing the new open shadow root, you might notice now that our element is completely broken when we try to run it:

See the Pen
Dialog example using template with shadow root
by Caleb Williams (@calebdwilliams)
on CodePen.

This is because all of the content we had before was added to and manipulated in the traditional DOM (what we’ll call the light DOM). Now that our element has a shadow DOM attached, there is no outlet for the light DOM to render. Let’s start fixing these issues by moving our content to the shadow DOM:

class OneDialog extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.close = this.close.bind(this);
  }
  
  connectedCallback() {
    const { shadowRoot } = this;
    const template = document.getElementById('one-dialog');
    const node = document.importNode(template.content, true);
    shadowRoot.appendChild(node);
    
    shadowRoot.querySelector('button').addEventListener('click', this.close);
    shadowRoot.querySelector('.overlay').addEventListener('click', this.close);
    this.open = this.open;
  }

  disconnectedCallback() {
    this.shadowRoot.querySelector('button').removeEventListener('click', this.close);
    this.shadowRoot.querySelector('.overlay').removeEventListener('click', this.close);
  }
  
  set open(isOpen) {
    const { shadowRoot } = this;
    shadowRoot.querySelector('.wrapper').classList.toggle('open', isOpen);
    shadowRoot.querySelector('.wrapper').setAttribute('aria-hidden', !isOpen);
    if (isOpen) {
      this._wasFocused = document.activeElement;
      this.setAttribute('open', '');
      document.addEventListener('keydown', this._watchEscape);
      this.focus();
      shadowRoot.querySelector('button').focus();
    } else {
      this._wasFocused && this._wasFocused.focus && this._wasFocused.focus();
      this.removeAttribute('open');
      document.removeEventListener('keydown', this._watchEscape);
    }
  }
  
  close() {
    this.open = false;
  }
  
  _watchEscape(event) {
    if (event.key === 'Escape') {
        this.close();   
    }
  }
}

customElements.define('one-dialog', OneDialog);

The major changes to our dialog so far are actually relatively minimal, but they carry a lot of impact. For starters, all our selectors (including our style definitions) are internally scoped. For example, our dialog template only has one button internally, so our CSS only targets button { ... }, and those styles don’t bleed out to the light DOM.

We are, however, still reliant on the template that is external to our element. Let’s change that by removing the markup from our template and dropping it into our shadow root’s innerHTML.

See the Pen
Dialog example using only shadow root
by Caleb Williams (@calebdwilliams)
on CodePen.

Including content from the light DOM

The shadow DOM specification includes a means for allowing content from outside the shadow root to be rendered inside of our custom element. For those of you who remember AngularJS, this is a similar concept to ng-transclude or using props.children in React. With Web Components, this is done using the <slot> element.

A simple example would look like this:

<div>
  <span>world <!-- this would be inserted into the slot element below --></span>
  <#shadow-root><!-- pseudo code -->
    <p>Hello <slot></slot></p>
  </#shadow-root>
</div>

A given shadow root can have any number of slot elements, which can be distinguished with a name attribute. The first slot inside of the shadow root without a name, will be the default slot and all content not otherwise assigned will flow inside that node. Our dialog really needs two slots: a heading and some content (which we’ll make default).

See the Pen
Dialog example using shadow root and slots
by Caleb Williams (@calebdwilliams)
on CodePen.

Go ahead and change the HTML portion of our dialog and see the result. Any content inside of the light DOM is inserted into the slot to which it is assigned. Slotted content remains inside the light DOM although it is rendered as if it were inside the shadow DOM. This means that these elements are still fully style-able by a consumer who might want to control the look and feel of their content.

A shadow root’s author can style content inside the light DOM to a limited extent using the CSS ::slotted() pseudo-selector; however, the DOM tree inside slotted is collapsed, so only simple selectors will work. In other words, we wouldn't be able to style a <strong> element inside a <p> element within the flattened DOM tree in our previous example.

The best of both worlds

Our dialog is in a good state now: it has encapsulated, semantic markup, styles and behavior; however, some consumers of our dialog might still want to define their own template. Fortunately, by combining two techniques we’ve already learned, we can allow authors to optionally define an external template.

To do this, we will allow each instance of our component to reference an optional template ID. To start, we need to define a getter and setter for our component’s template.

get template() {
  return this.getAttribute('template');
}

set template(template) {
  if (template) {
    this.setAttribute('template', template);
  } else {
    this.removeAttribute('template');
  }
  this.render();
}

Here we’re doing much the same thing that we did with our open property by tying it directly to its corresponding attribute. But at the bottom, we’re introducing a new method to our component: render. We are going to use our render method to insert our shadow DOM’s content and remove that behavior from the connectedCallback; instead, we will call render when our element is connected:

connectedCallback() {
  this.render();
}

render() {
  const { shadowRoot, template } = this;
  const templateNode = document.getElementById(template);
  shadowRoot.innerHTML = '';
  if (templateNode) {
    const content = document.importNode(templateNode.content, true);
    shadowRoot.appendChild(content);
  } else {
    shadowRoot.innerHTML = `<!-- template text -->`;
  }
  shadowRoot.querySelector('button').addEventListener('click', this.close);
  shadowRoot.querySelector('.overlay').addEventListener('click', this.close);
  this.open = this.open;
}

Our dialog now has some really basic default stylings, but also gives consumers the ability to define a new template for each instance. If we wanted, we could even use attributeChangedCallback to make this component update based on the template it’s currently pointing to:

static get observedAttributes() { return ['open', 'template']; }

attributeChangedCallback(attrName, oldValue, newValue) {
  if (newValue !== oldValue) {
    switch (attrName) {
      /** Boolean attributes */
      case 'open':
        this[attrName] = this.hasAttribute(attrName);
        break;
      /** Value attributes */
      case 'template':
        this[attrName] = newValue;
        break;
    }
  }
}

See the Pen
Dialog example using shadow root, slots and template
by Caleb Williams (@calebdwilliams)
on CodePen.

In the demo above, changing the template attribute on our <one-dialog> element will alter which design is being used when the element is rendered.

Strategies for styling the shadow DOM

Currently, the only reliable way to style a shadow DOM node is by adding a <style> element to the shadow root’s inner HTML. This works fine in almost every case as browsers will de-duplicate stylesheets across these components, where possible. This does tend to add a bit of memory overhead, but generally not enough to notice.

Inside of these style tags, we can use CSS custom properties to provide an API for styling our components. Custom properties can pierce the shadow boundary and effect content inside a shadow node.

“Can we use a <link> element inside of a shadow root?” you might ask. And, in fact, we can. The trouble comes when trying to reuse this component across multiple applications as the CSS file might not be saved in a consistent location throughout all apps. However, if we are certain as to the element’s stylesheet location, then using <link> is an option. The same holds true for including an @import rule in a style tag.

It is also worth mentioning that not all components need the kind of styling we're using here. Using the CSS :host and :host-context selectors, we can simply define more primitive components as block-level elements and allow consumers to provide classes to style things like background colors, font settings, and more.

Our dialog, on the other hand, is fairly complex. Something like a listbox (comprised of a label and an checkbox input) is not and can be just merely as a surface for native element composition. That is equally as valid a styling strategy as is being more explicit about styles (say for design systems purposes where all checkboxes might look a certain way). It largely depends on your use case.

CSS custom properties

One of the benefits of using CSS custom properties — also called CSS variables — is that they bleed through the shadow DOM. This is by design, giving component authors a surface for allowing theming and styling of their components from the outside. It is important to note, however, that since CSS cascades, changes to custom properties made inside a shadow root do not bleed back up.

See the Pen
CSS custom properties and shadow DOM
by Caleb Williams (@calebdwilliams)
on CodePen.

Go ahead and comment out or remove the variables set in the CSS panel of the demo above and see how this impacts the rendered content. Afterward, you can take a look at the styles in the shadow DOM’s innerHTML, you’ll see how the shadow DOM can define its own property that won’t affect the light DOM.

Constructible stylesheets

At the time of this writing, there is a proposed web feature that will allow for more modular styling of shadow DOM and light DOM elements using constructible stylesheets that has already landed in Chrome 73 and received positive signaling from Mozilla.

This feature would allow authors to define stylesheets in their JavaScript files similar to how they would write normal CSS and share those styles across multiple nodes. So, a single stylesheet could be appended to multiple shadow roots and potentially the document as well.

const everythingTomato = new CSSStyleSheet();
everythingTomato.replace('* { color: tomato; }');

document.adoptedStyleSheets = [everythingTomato];

class SomeCompoent extends HTMLElement {
  constructor() {
    super();
    this.adoptedStyleSheets = [everythingTomato];
  }
  
  connectedCallback() {
    this.shadowRoot.innerHTML = `<h1>CSS colors are fun</h1>`;
  }
}

In the above example, the everythingTomato stylesheet would be simultaneously applied to the shadow root and to the document’s body. This feature would be very useful for teams creating design systems and components that are intended to be shared across multiple applications and frameworks.

In the next demo, we can see a really basic example of how this can be utilized and the power that constructble stylesheets offer.

See the Pen
Construct style sheets demo
by Caleb Williams (@calebdwilliams)
on CodePen.

In this demo, we construct two stylesheets and append them to the document and to the custom element. After three seconds, we remove one stylesheet from our shadow root. For those three seconds, however, the document and the shadow DOM share the same stylesheet. Using the polyfill included in that demo, there are actually two style elements present, but Chrome runs this natively.

That demo also includes a form for showing how a sheet’s rules can easily and effectively changed asynchronously as needed. This addition to the web platform can be a powerful ally for those creating design systems that span multiple frameworks or site authors who want to provide themes for their websites.

There is also a proposal for CSS Modules that could eventually be used with the adoptedStyleSheets feature. If implemented in its current form, this proposal would allow importing CSS as a module much like ECMAScript modules:

import styles './styles.css';

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

Part and theme

Another feature that is in the works for styling Web Components are the ::part() and ::theme() pseudo-selectors. The ::part() specification will allow authors to define parts of their custom elements that have a surface for styling:

class SomeOtherComponent extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>h1 { color: rebeccapurple; }</style>
      <h1>Web components are <span part="description">AWESOME</span></h1>
    `;
  }
}
    
customElements.define('other-component', SomeOtherComponent);

In our global CSS, we could target any element that has a part called description by invoking the CSS ::part() selector.

other-component::part(description) {
  color: tomato;
}

In the above example, the primary message of the <h1> tag would be in a different color than the description part, giving custom element authors the ability to expose styling APIs for their components and maintain control over the pieces they want to maintain control over.

The difference between ::part() and ::theme() is that ::part() must be specifically selected whereas ::theme() can be nested at any level. The following would have the same effect as the above CSS, but would also work for any other element that included a part="description" in the entire document tree.

:root::theme(description) {
  color: tomato;
}

Like constructible stylesheets, ::part() has landed in Chrome 73.

Wrapping up

Our dialog component is now complete, more-or-less. It includes its own markup, styles (without any outside dependencies) and behaviors. This component can now be included in projects that use any current or future frameworks because they are built against the browser specifications instead of third-party APIs.

Some of the core controls are a little verbose and do rely on at least a moderate knowledge of how the DOM works. In our final article, we will discuss higher-level tooling and how to incorporate with popular frameworks.

Article Series:

  1. An Introduction to Web Components
  2. Crafting Reusable HTML Templates
  3. Creating a Custom Element from Scratch
  4. Encapsulating Style and Structure with Shadow DOM (This post)
  5. Advanced Tooling for Web Components

The post Encapsulating Style and Structure with Shadow DOM appeared first on CSS-Tricks.

Shadow Root DOM and Custom HTML Tags Automation Using Selenium

In this article, we will try to solve a multi-level shadow DOM automation problem using an open-source GitHub project that will let you test multi-level shadow DOM elements in your Java Selenium project. We will use Chrome as the main browser for testing this feature since Chrome v53+ fully supports shadow DOM.

Now let's talk about how this all started. I was working with a project that works on a workflow automation tool called Frevvo. In one of the improvements, we started using the Google Polymer framework that creates shadow DOM elements. As our automation framework was developed with Java Selenium, we were stuck, as the Selenium tool was not able to find elements under the shadow DOM.

An Introduction to Web Components

Front-end development moves at a break-neck pace. This is made evident by the myriad articles, tutorials, and Twitter threads bemoaning the state of what once was a fairly simple tech stack. In this article, I’ll discuss why Web Components are a great tool to deliver high-quality user experiences without complicated frameworks or build steps and that don’t run the risk of becoming obsolete. In subsequent articles of this five-part series, we will dive deeper into each of the specifications.

This series assumes a basic understanding of HTML, CSS, and JavaScript. If you feel weak in one of those areas, don’t worry, building a custom element actually simplifies many complexities in front-end development.

Article Series:

  1. An Introduction to Web Components (This post)
  2. Crafting Reusable HTML Templates (Coming soon!)
  3. Creating a Custom Element from Scratch (Coming soon!)
  4. Encapsulating Style and Structure with Shadow DOM (Coming soon!)
  5. Advanced Tooling for Web Components (Coming soon!)

What are Web Components, anyway?

Web Components consist of three separate technologies that are used together:

  1. Custom Elements. Quite simply, these are fully-valid HTML elements with custom templates, behaviors and tag names (e.g. <one-dialog>) made with a set of JavaScript APIs. Custom Elements are defined in the HTML Living Standard specification.
  2. Shadow DOM. Capable of isolating CSS and JavaScript, almost like an <iframe>. This is defined in the Living Standard DOM specification.
  3. HTML templates. User-defined templates in HTML that aren’t rendered until called upon. The <template> tag is defined in the HTML Living Standard specification.

These are what make up the Web Components specification.

HTML Imports is likely to be the fourth technology in the stack, but it has yet to be implemented in any of the big four browsers. The Chrome team has announced it an intent to implement them in a future release.

Web Components are generally available in all of the major browsers with the exception of Microsoft Edge and Internet Explorer 11, but polyfills exist to fill in those gaps.

Referring to any of these as Web Components is technically accurate because the term itself is a bit overloaded. As a result, each of the technologies can be used independently or combined with any of the others. In other words, they are not mutually exclusive.

Let’s take a quick look at each of those first three. We’ll dive deeper into them in other articles in this series.

Custom elements

As the name implies, custom elements are HTML elements, like <div>, <section> or <article>, but something we can name ourselves that are defined via a browser API. Custom elements are just like those standard HTML elements — names in angle brackets — except they always have a dash in them, like <news-slider> or <bacon-cheeseburger>. Going forward, browser vendors have committed not to create new built-in elements containing a dash in their names to prevent conflicts.

Custom elements contain their own semantics, behaviors, markup and can be shared across frameworks and browsers.

class MyComponent extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `<h1>Hello world</h1>`;
  }
}
    
customElements.define('my-component', MyComponent);

See the Pen
Custom elements demo
by Caleb Williams (@calebdwilliams)
on CodePen.

In this example, we define <my-component>, our very own HTML element. Admittedly, it doesn’t do much, however this is the basic building block of a custom element. All custom elements must in some way extend an HTMLElement in order to be registered with the browser.

Custom elements exist without third-party frameworks and the browser vendors are dedicated to the continued backward compatibility of the spec, all but guaranteeing that components written according to the specifications will not suffer from breaking API changes. What’s more, these components can generally be used out-of-the-box with today’s most popular frameworks, including Angular, React, Vue, and others with minimal effort.

Shadow DOM

The shadow DOM is an encapsulated version of the DOM. This allows authors to effectively isolate DOM fragments from one another, including anything that could be used as a CSS selector and the styles associated with them. Generally, any content inside of the document’s scope is referred to as the light DOM, and anything inside a shadow root is referred to as the shadow DOM.

When using the light DOM, an element can be selected by using document.querySelector('selector') or by targeting any element’s children by using element.querySelector('selector'); in the same way, a shadow root’s children can be targeted by calling shadowRoot.querySelector where shadowRoot is a reference to the document fragment — the difference being that the shadow root’s children will not be select-able from the light DOM. For example, If we have a shadow root with a <button> inside of it, calling shadowRoot.querySelector('button') would return our button, but no invocation of the document’s query selector will return that element because it belongs to a different DocumentOrShadowRoot instance. Style selectors work in the same way.

In this respect, the shadow DOM works sort of like an <iframe> where the content is cut off from the rest of the document; however, when we create a shadow root, we still have total control over that part of our page, but scoped to a context. This is what we call encapsulation.

If you’ve ever written a component that reuses the same id or relies on either CSS-in-JS tools or CSS naming strategies (like BEM), shadow DOM has the potential to improve your developer experience.

Imagine the following scenario:

<div>
  <div id="example">
    <!-- Pseudo-code used to designate a shadow root -->
    <#shadow-root>
      <style>
      button {
        background: tomato;
        color: white;
      }
      </style>
      <button id="button">This will use the CSS background tomato</button>
    </#shadow-root>
  </div>
  <button id="button">Not tomato</button>
</div>

Aside from the pseudo-code of <#shadow-root> (which is used here to demarcate the shadow boundary which has no HTML element), the HTML is fully valid. To attach a shadow root to the node above, we would run something like:

const shadowRoot = document.getElementById('example').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `<style>
button {
  color: tomato;
}
</style>
<button id="button">This will use the CSS color tomato <slot></slot></button>`;

A shadow root can also include content from its containing document by using the <slot> element. Using a slot will drop user content from the outer document at a designated spot in your shadow root.

See the Pen
Shadow DOM style encapsulation demo
by Caleb Williams (@calebdwilliams)
on CodePen.

HTML templates

The aptly-named HTML <template> element allows us to stamp out re-usable templates of code inside a normal HTML flow that won’t be immediately rendered, but can be used at a later time.

<template id="book-template">
  <li><span class="title"></span> &mdash; <span class="author"></span></li>
</template>

<ul id="books"></ul>

The example above wouldn’t render any content until a script has consumed the template, instantiated the code and told the browser what to do with it.

const fragment = document.getElementById('book-template');
const books = [
  { title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },
  { title: 'A Farewell to Arms', author: 'Ernest Hemingway' },
  { title: 'Catch 22', author: 'Joseph Heller' }
];

books.forEach(book => {
  // Create an instance of the template content
  const instance = document.importNode(fragment.content, true);
  // Add relevant content to the template
  instance.querySelector('.title').innerHTML = book.title;
  instance.querySelector('.author').innerHTML = book.author;
  // Append the instance ot the DOM
  document.getElementById('books').appendChild(instance);
});

Notice that this example creates a template (<template id="book-template">) without any other Web Components technology, illustrating again that the three technologies in the stack can be used independently or collectively.

Ostensibly, the consumer of a service that utilizes the template API could write a template of any shape or structure that could be created at a later time. Another page on a site might use the same service, but structure the template this way:

<template id="book-template">
  <li><span class="author"></span>'s classic novel <span class="title"></span></li>
</template>

<ul id="books"></ul>

See the Pen
Template example
by Caleb Williams (@calebdwilliams)
on CodePen.

That wraps up our introduction to Web Components

As web development continues to become more and more complicated, it will begin to make sense for developers like us to begin deferring more and more development to the web platform itself which has continued to mature. The Web Components specifications are a set of low-level APIs that will continue to grow and evolve as our needs as developers evolve.

In the next article, we will take a deeper look at the HTML templates part of this. Then, we’ll follow that up with a discussion of custom elements and shadow DOM. Finally, we’ll wrap it all up by looking at higher-level tooling and incorporation with today’s popular libraries and frameworks.

Article Series:

  1. An Introduction to Web Components (This post)
  2. Crafting Reusable HTML Templates (Coming soon!)
  3. Creating a Custom Element from Scratch (Coming soon!)
  4. Encapsulating Style and Structure with Shadow DOM (Coming soon!)
  5. Advanced Tooling for Web Components (Coming soon!)

The post An Introduction to Web Components appeared first on CSS-Tricks.

Styling a Web Component

This confused me for a bit here so I'm writing it out while it's fresh in mind. Just because you're using a web component doesn't mean the styles of it are entirely isolated. You might have content within a web component that is styled normally along with the rest of your website. Like this:

See the Pen Web Component with Global Styles (because no Shadow DOM) by Chris Coyier (@chriscoyier) on CodePen.

That <whats-up> element isolated the JavaScript-powered functionality of itself by attaching a click handler to the <button> inside of it. But the styling of that button comes from global CSS applied to that page.

Moving the template inside the web component

But let's say we move that <button> into the web component, so we can use <whats-up> all by itself. We could do that by .innerHTML'ing the custom element:

See the Pen Web Component with Global Styles (because no Shadow DOM) by Chris Coyier (@chriscoyier) on CodePen.

Again, entirely styled by the global CSS. Cool. That may be desirable. It also might not be desirable. Perhaps you're looking to web components to isolate styles for you.

Shadow DOMing the template

Web components can isolate styles (and abstract away HTML implementation) via the Shadow DOM. Here's that same component, using Shadow DOM instead:

See the Pen Web Component with Local Styles by Chris Coyier (@chriscoyier) on CodePen.

Note that the functionality still works (although we had to querySelector through the shadowRoot), but we've totally lost the global styling. The Shadow DOM boundary (shadow root) prevents styling coming in or going out (sorta like an iframe).

Shadow Root

There is no global way to penetrate that boundary that I'm aware of, so if you want to bring styles in, you gotta bring them into the template.

Move the styles (inline) inside the web component

See the Pen Web Component with Local Styles by Chris Coyier (@chriscoyier) on CodePen.

This would be highly obnoxious if you both really wanted to use the Shadow DOM but also wanted your global styles. It's funny that there is a Shadow DOM "mode" for open and closed for allowing or disallowing JavaScript in and out, but not CSS.

If that's you, you'll probably need to @import whatever global stylesheets you can to bring in those global styles and hope they are cached and the browser is smart about it in such a way that it isn't a big performance hit.

Link to external styles instead

I'll use CodePen's direct link to CSS feature to import the styles from the Pen itself into the web component:

See the Pen Web Component with Local Styles by Chris Coyier (@chriscoyier) on CodePen.

Apparently, there is no way to avoid somewhat of a Flash-Of-Unstyled-Component this way though, so inlining styles is recommended until there is.

Custom properties go through the shadow DOM

Another important thing to know is that CSS custom properties penetrate the Shadow DOM! That's right, they do. You can select the web component in the CSS and set them there:

See the Pen Web Component with Custom Properties by Chris Coyier (@chriscoyier) on CodePen.

HTML you point to via a slot is globally stylable

So if you have like:

<my-module>
  <h2 slot="header">My Module</h2>
</my-module>

And where you define your shadow DOM you use that header:

<div class="module">
  <slot name="header"></slot>
</div>

Then the <h2> will be globally stylable, but that <div class="module"> will not.

See the Pen Slots and styling web components by Chris Coyier (@chriscoyier) on CodePen.

The post Styling a Web Component appeared first on CSS-Tricks.