0% found this document useful (0 votes)
108 views

Beginning Shadow DOM API Get Up and Running - Alex Libby

Uploaded by

talwarapappu
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
108 views

Beginning Shadow DOM API Get Up and Running - Alex Libby

Uploaded by

talwarapappu
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 183

Apress Pocket Guides

Apress Pocket Guides present concise summaries of cutting-edge


developments and working practices throughout the tech industry. Shorter in
length, books in this series aims to deliver quick-to-read guides that are easy
to absorb, perfect for the time-poor professional.

This series covers the full spectrum of topics relevant to the modern
industry, from security, AI, machine learning, cloud computing, web
development, product design, to programming techniques and business
topics too.

Typical topics might include:

• A concise guide to a particular topic, method, function

or framework

• Professional best practices and industry trends

• A snapshot of a hot or emerging topic

• Industry case studies

• Concise presentations of core concepts suited for

students and those interested in entering the tech

industry

• Short reference guides outlining ‘need-to-know’

concepts and practices

More information about this series at https://link.springer.com/

bookseries/17385.

Beginning Shadow
DOM API

Get Up and Running

with Shadow DOM for Web

Applications

Alex Libby

Beginning Shadow DOM API: Get Up and Running with Shadow DOM
for

Web Applications

Alex Libby

Belper, Derbyshire, UK

ISBN-13 (pbk): 979-8-8688-0248-5

ISBN-13 (electronic): 979-8-8688-0249-2

https://doi.org/10.1007/979-8-8688-0249-2

Copyright © 2024 by Alex Libby

This work is subject to copyright. All rights are reserved by the Publisher,
whether the whole or part of the material is concerned, specifically the rights
of translation, reprinting, reuse of illustrations, recitation, broadcasting,
reproduction on microfilms or in any other physical way, and transmission or
information storage and retrieval, electronic adaptation, computer software,
or by similar or dissimilar methodology now known or hereafter developed.

Trademarked names, logos, and images may appear in this book. Rather than
use a trademark symbol with every occurrence of a trademarked name, logo,
or image we use the names, logos, and images only in an editorial fashion
and to the benefit of the trademark owner, with no intention of infringement
of the trademark.
The use in this publication of trade names, trademarks, service marks, and
similar terms, even if they are not identified as such, is not to be taken as an
expression of opinion as to whether or not they are subject to proprietary
rights.

While the advice and information in this book are believed to be true and
accurate at the date of publication, neither the authors nor the editors nor the
publisher can accept any legal responsibility for any errors or omissions that
may be made. The publisher makes no warranty, express or implied, with
respect to the material contained herein.

Managing Director, Apress Media LLC: Welmoed Spahr

Acquisitions Editor: James Robinson Prior

Development Editor: James Markham

Editorial Assistant: Gryffin Winkler

Cover designed by eStudioCalamar

Distributed to the book trade worldwide by Springer Science+Business


Media New York, 1

New York Plaza, Suite 4600, New York, NY 10004-1562, USA. Phone 1-
800-SPRINGER, fax (201) 348-4505, e-mail [email protected],
or visit www.springeronline.com. Apress Media, LLC is a California LLC
and the sole member (owner) is Springer Science + Business Media Finance
Inc (SSBM Finance Inc). SSBM Finance Inc is a Delaware corporation.

For information on translations, please e-mail


[email protected]; for reprint, paperback, or audio
rights, please e-mail [email protected].

Apress titles may be purchased in bulk for academic, corporate, or


promotional use. eBook versions and licenses are also available for most
titles. For more information, reference our Print and eBook Bulk Sales web
page at http://www.apress.com/bulk-sales.
Any source code or other supplementary material referenced by the author in
this book is available to readers on GitHub. For more detailed information,
please visit https://www.apress.

com/gp/services/source-code.

Paper in this product is recyclable

This is dedicated to my family, with thanks for their love and support while
writing this book.

Table of Contents

About the Author


������������������������������
������������������������������
���������������������xi About the Technical
Reviewer
������������������������������
�����������������������������xii
i Acknowledgments
������������������������������
������������������������������
������������������xv Introduction
������������������������������
������������������������������
��������������������������xvii
Chapter 1: Getting Started
������������������������������
������������������������������
��������1

Exploring the Different DOM Types2

Creating a Simple Example 3

Understanding What Happened 9


Considering the Bigger Picture 15

Understanding Terminology Around the API 16

Creating the Component – A Process 18

Browser Support for the API19

Practical Considerations for using the API 21

Composition Using Slots and Templates 24

Breaking Apart the Code Changes 28

Using Slots and Templates – A Postscript 30

Applying Styles – What’s Different? 31

Summary33

vii

Table of ConTenTs

Chapter 2: Creating Components


������������������������������
��������������������������35

Creating an Alert Dialog 36

Constructing the Markup 36

Creating a Toggle Switch 42

Constructing the Demo 50

Breaking Apart the Code 55

Testing the Components 57


Understanding What Happened 60

Packaging the Component for Release 62

Breaking Apart the Code Changes 70

Publishing the Component – A Postscript 71

Summary72

Chapter 3: Applying Shadow DOM API in Apps


������������������������������
����75

Working with Styling Frameworks 76

Exploring the Code Changes 80

Mounting a React App Using Shadow DOM API 83

Understanding What Happened 89

Implications of SEO and the API 90

Comparing with Bing 93

Challenges with Server-Side Rendering – A Postscript 95

Summary97

Chapter 4: Supporting Other Frameworks


������������������������������
������������99

Adding Shadow DOM API Support to React Components 99

Breaking Apart the Code Changes 104

Exploring Other Framework Examples 106


First Example: Using Svelte 106

viii

Table of ConTenTs

Second Example: Exploring Alpinejs 118

Assessing Performance of the API 124

Will Web Components Replace Tools Such As React? 126

And Now for Something Completely Different… 127

Summary128

Appendix: API Reference


������������������������������
������������������������������
������129

Index
������������������������������
������������������������������
������������������������������
�������133

ix

About the Author

Alex Libby is a front-end developer and seasoned computer book author,


who hails from England. His passion for all things open source dates back to
his student days, when he first came across web development, and he has
been hooked ever since. His daily work involves extensive use of JavaScript,
HTML, and CSS to manipulate existing website content.

Alex enjoys tinkering with different open source libraries to see how they
work. He has spent a stint maintaining the jQuery Tools library and enjoys
writing about open source technologies, principally for front-end UI
development.

xi

About the Technical Reviewer

Kenneth Fukizi is a passionate software

engineer, architect, and consultant with

experience in programming on different

tech stacks. Prior to dedicated software

development, he worked as a lecturer and

was then head of IT at different organizations.

He has domain experience working with

technology for companies mainly in the

financial sector. When he’s not working, he

likes reading up on emerging technologies and strives to be an active


member of the software community.
Kenneth currently leads a community of African developers, through a
startup company called AfrikanCoder.

xiii

Acknowledgments

Writing a book can be a long but rewarding process – I still remember


starting to write my first book back in 2011, which seems such a long time
ago!

Whatever the size or nature of any book, it is not possible to complete it


without the help of other people. To that end, I would like to offer a huge
vote of thanks to my editors – in particular, Sowmya Thodur and James
Robinson-Prior; my thanks also to Kenneth Fukizi as my technical reviewer,
James Markham for his help during the process, and others at Apress for
getting this book into print. All have made writing this book a painless and
enjoyable process, even with the edits!

My thanks also to my family for being understanding and supporting me


while writing. I frequently spend a lot of late nights writing alone, or pass up
times when I should be with them, so their words of

encouragement and support have been a real help in getting past those
bumps in the road and producing the finished book that you now hold in
your hands.

xv

Introduction

Beginning Shadow DOM API is for people who want to learn how to quickly
implement the Shadow DOM API to create encapsulated

components that maintain the styles we want to use, without the need for
external tools such as React.

This mini project-oriented book first examines what the Shadow DOM
API is all about and the benefits of using it and then touches on where it fits
into the landscape as a browser API. We’ll examine the various parts of the
API and understand why it may not be necessary to use a heavyweight tool
such as React when the emphasis is on speed and efficiency in any project.

Throughout this book, I’ll take you on a journey through using the API,
exploring how easy it is to create simple examples that we can extend and
make more dynamic with the minimum of fuss. We’ll use all of the
techniques we gain from exploring the API, to make a more complex

component – I’ll show you the process from start to finish, including
examples of how we can see it in action. It may not be production-ready
from the get-go, but it will give us a good grounding to develop it into
something more complex at a later date. We’ll even take a look at using the
API in noncomponent code and explore how tools such as React can use the
API too – should we still need to use them!

Beginning Shadow DOM API uses nothing more than standard

JavaScript, CSS, and HTML, three of the most powerful tools available for
developers: you can enhance, extend, and configure your components as
requirements dictate. With this API, the art of possible is only limited by the
extent of your imagination and the power of JavaScript, HTML, and
Node.js.

xvii

CHAPTER 1

Getting Started

Coding is a messy business. Ouch. Why?

We all work on projects – some for clients, some for personal

development; chances are we need to apply fixes to our code at some point,
then apply fixes on fixes. Very soon, things become messy and
unmanageable – sound familiar?
We need a different approach – one way to achieve this is to create
components or modules that serve a single responsibility, such as a custom
button. We can encapsulate modular code, so we don’t need to worry about
how it all works inside: pass in the various properties and out spits a button
that suits our needs!

Sounds sensible, right? Typically, we might reach for React and knock out
something suitable ... but hold on for a moment. What if I told you we could
build using one framework but use it across multiple different frameworks?
Yes, I’m talking about creating something in Svelte and using it in Svelte,
React, Angular, Vue, and more.

To do this, we need to create web components – these are not your

typical standard components that you might create in something like React
and only use in React. There is much more to it than that – let me introduce
you to what makes it all possible: the Shadow DOM API!

Throughout this book, we’ll look at this API, dig into a few examples, and
understand how it helps us to create truly reusable components. Sure, they
may have a few quirks compared to standard components, but that’s part of
the fun – we’ll see why using this API means we don’t always have to use a
sledgehammer to crack a nut....

© Alex Libby 2024

A. Libby, Beginning Shadow DOM API, Apress Pocket Guides,

https://doi.org/10.1007/979-8-8688-0249-2_1

CHapTeR 1 GeTTinG STaRTeD

Exploring the Different DOM Types

Okay – so before I get too melodramatic (“dom, da, da, dom...” –


sorry!), there’s something we need to cover first: What the heck is the
Shadow DOM?

As developers, we all know about the DOM (or “Real DOM”) – as the

HTML tree of the website, we can access any element within this object
using object dot notation. The trouble is it has to update when there’s been a
change to the DOM; this can get very expensive if we have to make many
changes!

You may also see references to Light DOM – this is the same thing as the
Real DOM; it’s the DOM we know inside an HTML element.

Can we improve on this? Enter the alternative – Virtual DOM. It forms the
basis of most current frameworks, such as React. It compiles a copy of the
real DOM into JavaScript before applying any changes to the copy.

React then compares the copy to detect changes and update only those
elements.

Okay, as developers, we may be used to seeing the (Real) DOM and

the Virtual DOM (when used in frameworks such as React). The thing is,
how do they compare to the Shadow DOM? This is where things get a little
different: instead of taking a copy of the real DOM into memory (which
becomes the Virtual DOM), Shadow DOM allows us to add pockets of

hidden DOM trees to elements in the real DOM tree. These are independent
of each other – changes to one will not affect the other (although this might
not be so true for styling, but we’ll come to that later in the book).

Right – enough of the theory: let’s get down and dirty with some code!

We’ll go through the various options available in the API shortly, but before
we do so, let’s knock up a quick couple of examples so you can see the API
in action.

2
CHapTeR 1 GeTTinG STaRTeD

Creating a Simple Example

Creating an instance of the Shadow DOM API is easier than it might initially
seem. In addition to the code we want to use to add our feature or
component, we only need to use the .attachShadow() method to initiate that
instance. Once initialized, we can add whatever code we like and apply
styles to our shadow element as necessary.

To see what I mean, let’s look at a simple example using nothing more than
vanilla JavaScript. For this demo, we will create the time-honored equivalent
of the “Hello World” program that every developer starts with when they
first start learning to code:

BUILDING AN EXAMPLE PART 1: VANILLA JS

To create our demo, follow these steps:

1. First, head over to www.codepen.io, then hit Create ➤ pen on the left.

i’ve used Codepen for convenience – there are a lot of ways to create this
demo, which will work equally well. if you prefer to use a site such as
CodeSandbox (www.codesandbox.io), or even build it locally, then please
feel free to do so.

2. in the HTML section, go ahead and add this markup:

<div id="foo"></div>

<p class="text">Hello World!</p>

3
CHapTeR 1 GeTTinG STaRTeD

3. next, in the JS section, add this code:

const existingElement = document.getElementById('foo');

const shadow = existingElement.attachShadow({mode:

'open'});

const message = document.createElement('p');

message.setAttribute('class', 'text');

message.textContent = 'Hello World!';

const styles = document.createElement('style');

styles.textContent = '.text { color: red };';

shadow.appendChild(styles);

shadow.appendChild(message);

4. Finally, in the CSS section, add this:

.body { margin: 10px }

.text {font-weight: bold; text-transform: uppercase;}

5. Codepen will now show the results in the output window, as

shown in Figure 1-1.

Figure 1-1. Example of using the Shadow DOM API (in vanilla JavaScript)

6. at face value, it looks like the same two pieces of text, albeit styled
differently. The magic, though, is when we look at the

elements tab of the Developer Console in Chrome (or your


browser’s equivalent) – try searching for div id="foo" as shown in Figure 1-
2.

CHapTeR 1 GeTTinG STaRTeD

Figure 1-2. An example of the Shadow DOM API in use

Don’t worry if this doesn’t all make sense yet – we will go through this in
more detail later! For now, though, the key message here is that you will see
#shadow-root displayed in your code (as shown in Figure 1-2); if this is
present, then we are using the API.

You can see an example of this demo in a Codepen at https://

codepen.io/Alex-Libby/pen/WNPNyeP; the code is also available in the


download accompanying this book.

Let’s move on and work through a quick example using a more

modern framework – React. As the API forms the basis for creating web
components, we will base it around a simple example so you can see what it
would look like as an actual component (and not inline, as we did in the
previous demo).

For this next demo, I’ve elected to use Vite (www.vitejs.dev) as the
basis for creating a placeholder site for our example. If you’ve not used it
before, it’s a great tool for creating a basic front-end skeleton of a React site,
with automatic browser reloading, quick boot, and folder structure for
adding content when ready. You may have heard about create-react-app –

Vite is very similar and is often seen as a more up-to-date replacement for
create-react-app.

CHapTeR 1 GeTTinG STaRTeD

BUILDING AN EXAMPLE PART 2: REACT

To set up our React example, follow these steps:

1. First, fire up a node.js terminal; then at the prompt, run this command:

$ npx create vite@latest

2. npm will prompt you if Vite is not installed; select yes to install it if
needed.

3. next, Vite will prompt for some information – when prompted,

use the responses highlighted here:

√ Project name: » shadow-dom-react

√ Select a framework: » React

√ Select a variant: » JavaScript + SWC

4. Once completed, you will see messages similar to these

appear – run the steps as outlined:

Scaffolding project in C:\shadow-dom-react...

Done. Now run:


cd shadow-dom-react

npm install

npm run dev

5. Vite will fire up its development server – if all is well, you should see this
appear:

VITE v4.4.11 ready in 347 ms

➜ Local: http://127.0.0.1:5173/

➜ Network: use --host to expose

➜ press h to show help

CHapTeR 1 GeTTinG STaRTeD

6. We now need to add some code – first, go ahead and add

a folder called components under \src. inside the \src\

components folder, create a new file, then add this code:

body {

font-family: sans-serif;

7. Save the file as my-customized-text.css. next, create a

new file in the same \src\components folder – this will contain

the code for our component. add this code, saving it at my-

customized- text.js:
import "./my-customized-text.css";

class FancyElement extends HTMLElement {

constructor() {

super();

this.ShadowRoot = this.attachShadow({ mode:

"open" });

this.ShadowRoot.innerHTML = "Shadow DOM imperative";

customElements.get("imperative-fancy-element") ||

customElements.define("imperative-fancy-element",

FancyElement);

8. We now need to make some changes to the existing files in

our “site” – first, crack open App.jsx and replace the entire

contents with this:

function App() {

return (

<imperative-fancy-element></imperative-

fancy-element>

7
CHapTeR 1 GeTTinG STaRTeD

<h1>Imperative Shadow DOM</h1>

);

export default App;

9. next, open index.html at the root of the project, and look for the line
starting with <meta name= "viewport" ..., then add this line immediately
below it:

<script

type="module"

src="./src/components/my-customized-text.js"

>

</script>

10. Finally, we will add a little styling to make it easier to view on screen –
it’s not compulsory, but we have the space, so let’s

use it! Crack open App.css, then add these styles:

body { width: 500px; margin: 100px auto; border: 1px

solid #000; padding: 10px; border-radius: 5px; }


11. Save any open files and close them.

12. Fire up (or switch to) your browser, then go ahead and browse to
http://127.0.0.1:5173 – if all is well, you should see the

screenshot shown in Figure 1-3.

Figure 1-3. The Shadow DOM API in use in a React site

CHapTeR 1 GeTTinG STaRTeD

Okay ... you’re probably wondering what is going on here! Although we’ve
not written much code in either demo, there are some essential points to note
when using the Shadow DOM API. The code uses principles not too
dissimilar to standard components, but there are some key

differences, so without further ado, let’s dive in and look at the code in more
detail.

Understanding What Happened

As developers, we are all familiar with the principles of the (real) DOM –

we can see all of the elements for a page within and interact with any as we
see fit. The same applies to using the Shadow DOM, but with some notable
differences:

• Any changes we make to a component that uses

the Shadow DOM API are encapsulated within the

component; if we change a font color, for example,

it won’t affect any component that sits outside the

Shadow DOM.

• Conversely, if we change the appearance of a


component outside of the Shadow DOM, then any

inside will not be affected.

• We can’t style components inside the Shadow DOM

using pure CSS (unlike ordinary components) – we

have to use JavaScript to alter their appearance.

In our first example, we rendered the words “Hello World!” on the

screen twice – nothing complicated about that. However – for the first
example, we used standard CSS to change its appearance: we changed the
font color to red using JavaScript. For the second one, we switched to using
CSS – applying a text-transform to uppercase and rendering the text in bold.

CHapTeR 1 GeTTinG STaRTeD

At first glance, this kind of change is a cinch to do using standard CSS –

so much so we developers could do it in our sleep! However, not all is what


it seems, particularly when we dig into the browser’s console log area.

Remember, if you will, what we did in our first demo (Figure 1-4).
Figure 1-4. A recap of our first demo

Although the same text appears twice, the first is in the Shadow DOM, and
the second sits outside. To understand how the Shadow DOM works, we first
need to explore some high-level concepts; we’ll return to our demo code
shortly.

Some High-Level Concepts

We should be aware of some terms when discussing the Shadow DOM –

we can see these in Figure 1-5.

10

CHapTeR 1 GeTTinG STaRTeD

Document Tree

document

Shadow Tree

Shadow

shadow

host

root

Shadow Boundary

Figure 1-5. A schematic of the Shadow DOM (source: MDN) What do they
all mean? Let’s dig into each of those terms in more

detail – I’ve listed them in Table 1-1.

Table 1-1. List of terms used in the Shadow DOM API


Term

Function

Shadow host

The regular DOM node.js that the Shadow DOM is attached to

Shadow tree

The DOM tree inside the Shadow DOM

Shadow

The place where the Shadow DOM ends and the regular DOM

boundary

begins

Shadow root

The root node.js of the shadow tree

11

CHapTeR 1 GeTTinG STaRTeD

A picture paints a thousand words, so let’s put that into a diagram –

Figure 1-5 shows the relationship a Shadow DOM has with the real DOM.

With that in mind, Figure 1-6 shows how our page will look once the
Shadow DOM is attached, flattened, and rendered on screen.

Document Tree

document

Shadow
host

Shadow Tree

Figure 1-6. The Shadow DOM, in a flattened page (source: MDN) It’s
important to note that you can affect the nodes in the Shadow DOM

in the same way as non-shadow nodes. For example, we might want to add
child elements or override existing styles in a component using a <style>
element. The critical point here, though, is we can’t change anything outside
of elements hosted in the Shadow DOM, which gives us great encapsulation
and allows us to style components as desired, especially if branding is
critical!

12

CHapTeR 1 GeTTinG STaRTeD

as an aside, you can override styling in a component inside the

Shadow DOM, but only if you pass the appropriate prop! We’ll come

back to this later in this chapter and the book.

Okay – now that we’ve explored, let’s return to the code and break it apart to
see how it works in more detail.

Breaking Apart the Demo Code

Although I know many of you will be familiar with tools such as React,
using the Shadow DOM API will be a new way of looking at a feature you
already use – probably without realizing or knowing it! Okay, granted: React
uses the Virtual DOM, whereas we’re working with the Shadow

DOM, but the basic principles are similar.

In our first demo, we explored how to initiate an instance of the


Shadow DOM using attachShadow(), noting that we must provide a mode
value: open or closed. Using this property opens up some interesting quirks,
but for now, knowing that it will control whether we can override styles in
our instance of the Shadow DOM is sufficient. Most of the remaining code
in this demo is standard JavaScript – we first create an instance of a p
element before using setAttribute to apply the text class and setting the
content to "Hello World!". At the same time, we turn the text red by creating
and adding a style element in JavaScript.

This next part is critical – we use the appendChild() method to apply both
the newly created p element (as message) and its styling (as styles).

This method is an essential part of the API – without it, we might create an
element but would never see it rendered in the DOM, shadow, or

otherwise! We finish off the first part of our demo by setting some basic
styling for the text elements.

13

CHapTeR 1 GeTTinG STaRTeD

Moving onto the second half of our demo, we switched to creating a similar
example using React – I’ve chosen to use Vite to generate the skeleton of our
demo, but any React-based framework will work in the same way. Once set
up, I created a simple stylesheet to apply some rudimentary styling for our
demo, followed by adding a block of markup that calls our component, as
well as importing it into the public version of index.html (I’ll come back to
the script in a moment). We then added some app styling to make our demo
more presentable before finally running the results in a browser.

Okay – back to that script. At first glance, it might look different, but if you
look closely, there are some similarities. This time, rather than inlining the
shadow content (as we did before), I created a basic component extending
the HTMLElement. Inside this, we add an instance of the Shadow DOM
using attachShadow() – same as before – before using innerHTML to add
some text. The magic happens in the next line: the customElements.
get() and customElements.define() calls will get the interface for our defined
component or define one if it does not exist. This is important, as this
command effectively makes our content or component available for any
framework to import it, not just the framework used to create it!

At this point, there is something important we need to cover – although I


have talked about using the Shadow DOM API, I have dropped in

references to web components on occasion. We can use the Shadow DOM

API in isolation, but it is an essential part of creating web components, so


exploring it in that context makes sense.

You will also notice that we used two different methods to initiate our
component – in the vanilla JavaScript demo, we inlined our code.

In contrast, in the React example, we used customElements(). Both are


equally fine to use, but each has different merits – we can choose which one
to use based on our needs.

Let’s crack on: We’ve explored how to use the API and seen how it fits into
the standard DOM (once it has been compiled and flattened). We’ve
discussed the Shadow DOM and its API and mentioned web components:
How does it all fit together?

14

CHapTeR 1 GeTTinG STaRTeD

Considering the Bigger Picture

Although this book is indeed about the Shadow DOM API, it’s essential to
understand how it fits into the bigger picture. So far, we’ve used
customElements, mentioned the Shadow DOM API a few times, and

touched on web components too!

This is one of those occasions where it’s hard to define a boundary between
all these concepts; they are all dependent on each other, so it can be
challenging to separate them. To really understand how it all works, we can
visualize these concepts as a pyramid (Figure 1-7).

Web

Components

Shadow DOM

• HTML Templates

API

• Slots

customElements

Figure 1-7. How the API fits into the bigger picture

At the bottom of the pile, and the basis for web components, sits the
customElements layer – this is what we use to identify our component when
implementing it in any framework. Next comes the Shadow

DOM API (and the subject of this book) – here, we use keywords such as
attachShadow() to hook our custom, encapsulated component

into the real DOM when used in our project. At the top, we have Web
Components – these will be the functionality we create based on using the
Shadow DOM API and features such as customElements.

15

CHapTeR 1 GeTTinG STaRTeD

However, you will notice a box to the side of our pyramid – even

though these two features are part of the wider Web Components

scope, they are optional. Using them will depend on your


circumstances. We’ll go through what this means later in this chapter.

Understanding Terminology Around the API

Now that we’ve explored a simple example and understood how it fits into
the broader picture that is web components, let’s turn our attention to
exploring the keywords in the API in more detail, as well as taking a look at
the basic process for creating a component using the Shadow DOM API.

The API is relatively small – it has four interfaces, six properties, and only
three events. Table 1-2 shows the key ones you will need to start using the
API.

To keep things simple, i will base this on keywords used in the vanilla
JavaScript example we created as our first demo.

16

CHapTeR 1 GeTTinG STaRTeD

Table 1-2. Some of the methods, properties, and interfaces of the API Term

Function

.attachShadow({mode: [

This is the key function we use once we

'open' | 'closed'] })

attach the Shadow host to the Shadow DOM.

document.

While not part of the api, we still need to

createElement('style');

create the element(s) that will sit inside the


message.setAttribute

Shadow host – as we have to use JavaScript

('class', 'text');

for this, we might use commands similar to

message.textContent =

those shown on the left.

'Hello World!';

shadow.appendChild()

This essential keyword adds our

content/elements to the Shadow host.

all interfaces, properties, and events are listed in the appendix at the rear of
this book.

In reality, we only need two commands: attachShadow() to create

our Shadow Host and appendChild() to add our chosen elements to the host.
The rest we use to create the content for our Shadow host – in an ideal
world, this would be static markup, but unfortunately, needs must:
JavaScript is what we have to use!

Okay – cast your mind back to the two-part demo we created at the

start of this book: there is an important question we need to answer. Is there


a standard process for creating a component that uses the Shadow DOM
API, or is it different, depending on our needs? We created a simple one
using text, but is it the same for all components?

17

CHapTeR 1 GeTTinG STaRTeD


Creating the Component – A Process

The great thing about using the Shadow DOM API (and therefore to create
web components) is that while each component of course will be different,
we can follow a basic process to implement the API in our component.

For this process, I’m assuming you are using ES6 or above and that you’re
not using a template (for now) to keep things simple. Earlier versions of the
ES standard will not work as they don’t use a class syntax, which is needed:

1. We first create a MyElement class that extends the

HTMLElement class.

2. Next, attach a Shadow DOM tree to the

custom element’s constructor using the

.attachShadow() method.

3. We then define the <my-element></myelement>

custom HTML tag that will represent the MyElement

JavaScript class in the HTML document, using the

customElements.define() method.

4. We need to import MyElement as an ES6 module.

5. Finally, use the <my-element></my-element> web

component on your page the same way as any

standard HTML element.

Seems pretty straightforward, right? This process is perfect for starting to


use the API – once we are more up to speed with it, we can then adapt it to
allow for using templates or slots (more on this later in this chapter).
The rest of the code will be whatever we want to include in the feature or
component we create using the API.

Okay – let’s move on: there is one important question we need to

answer. What is browser support like for this API? Is there anything we need
to consider, or will it ... just work?

18

CHapTeR 1 GeTTinG STaRTeD

Browser Support for the API

To answer that last question, browser support is pretty good, but it is also a
bit of a mixed bag! We must be mindful of a few quirks when using the API,
but this is mainly for smaller, lesser-known browsers.

Although the API is still (at the time of writing) considered a Working Draft,
the only browser that doesn’t support it is IE. Given that this browser is
slowly but surely being put out to pasture, it is no surprise! We can see the
details of desktop browser support in Figure 1-8.
Figure 1-8. Desktop browser support for the Shadow DOM API (source:
caniuse.com ) The critical thing to note is that while some API versions had
to be enabled manually in the browser to use them, this is no longer the case.

As long as you use a browser from the last three to four years, then you
should be OK.

19

CHapTeR 1 GeTTinG STaRTeD

Browser support for mobile devices is equally good – the only

browser not to offer support for the api is Opera Mini. its current usage is
minimal at just over 1% of all users (according to caniuse.

com), so it’s not something we need to worry about!

Now – not everything is as rosy as it might seem: a couple of things might


catch us out if we’re not careful! I’ve already mentioned that the API is still
technically in Working Draft status – it means that while it is stable and
browser support is good, it is likely to change. We already have two versions
of the API, so it’s essential to make sure you are aware of which one to use:

• The caniuse.com website has two versions of the API listed: v0 and v1. The
former, v0, has now been

deprecated and is no longer supported by any browser;

the version we are using is version 1, for which you can

see support at https://caniuse.com/shadowdomv1.

• There is also an unofficial version of the API, which

is designed to help with server-side rendering. A

check on the Can I Use website at https://caniuse.


com/declarative-shadow-dom shows that support is reasonably good,
although neither Firefox nor IE

supports it (the latter being no surprise). We’ll return to

this variation shortly when we talk about composition

and using slots in the API.

if you want to delve into the official documentation for this api, it’s available
on the W3 website, at www.w3.org/TR/shadow-dom/. it’s a bit of a dry read,
so forewarned is forearmed!

20

CHapTeR 1 GeTTinG STaRTeD

Okay – let’s crack on: we’ve seen two simple examples of using the Shadow
DOM: vanilla JavaScript or the React framework. It is a great start, but
before we get stuck into using the API, it would be helpful to look first at
some of the wider points we should consider when using it.

Practical Considerations for using the API

Over the last few pages, we’ve explored how to use the Shadow DOM API
to make the beginnings of a simple web component and how we can call it in
our code base. We also noted that the API forms the basis for creating web
components and that we can reuse these in any framework (as we will see
later in this book).

As we’ve seen how the API works technically, now is a perfect

opportunity to explore how we might use it in a more practical context.

While both the API and Web Components have come on in leaps and

bounds over the last few years, some aspects might be tricky to manage –

and could dissuade people from using the API.


I know some people will be tempted to jump straight to their

framework of choice (be it React, Vue, Angular, or something else), but I


would say: take a moment to consider what you are trying to achieve, and do
you really need a heavyweight framework to create the solution?

Of course, using the Shadow DOM API isn’t the best solution for

everything. I will bet that there will be occasions where using that
heavyweight framework will be like cracking a nut with a sledgehammer
when only a standard hammer is needed!

21

CHapTeR 1 GeTTinG STaRTeD

With that in mind, let’s take a look at some of the advantages of using the
Shadow DOM API in more detail:

• Lightweight: Get encapsulation of styles, names, etc.,

without the weight of tools such as React.

• Components are reusable across all frameworks.

• It helps prevent naming collisions – it’s not perfect, but

the encapsulation of styles will reduce the likelihood of

issues resulting from clashing names.

It goes to say that there are some practical aspects we need to be aware of –
while I hate using the word “disadvantages” (as it sounds so negative), we
still have to be mindful of a few points that could trip us up if we don’t
manage them in our code base! Let’s take a look at some of them in more
detail:

• The API doesn’t work with all HTML elements – this


will mean some cases where you might not be able to

use it if it the target element does not support it

• Reusability can be a concern – in our previous demos,

we’ve created examples implicitly, but in a real-world

scenario, we may end up with many components that

contain duplicate code!

• Information can be leaked from a component held

within the Shadow host, even though the Shadow DOM

isolates content such as CSS or JavaScript by default.

For example, using the ::part and ::theme pseudo-

elements can leak some styles into the main DOM, so

use them carefully!

i’ll come back to this in more detail when we look at styling

differences later in this chapter.

22

CHapTeR 1 GeTTinG STaRTeD

• Styling can be challenging, particularly as we may need

to override scoped styles in our feature or component.

If this is the case, then options are available – we’ll

explore more of these as we go through the book.


• Creating components using the standard Shadow DOM

will render fine when used client-side, but rendering

could be an issue server-side or where we may need

to render content as static HTML during the build

process.

• If we’re using form elements such as <input>,

<Select>, or <textarea>, these are not automatically

associated with the target form. We can work around

this, but it is a little clunky (for an example, see the PDF

that accompanies this book).

At first glance, it might seem as if some of these issues could be


showstoppers – while they may indeed affect how we use the API, there are
ways to get around them as long as we plan carefully. For example, it pays to
sanitize content used in the Shadow DOM, as it can be prone to attacks:
sanitizing content should be a de facto approach anyway! To see what I
mean about planning our approach, we will pick up on two of those issues:
styling and server-side rendering.

For the first one, we can use terms such as ::host, or CSS variables –

we’ll come to this shortly. For now, let’s look at how we adapt our building
process so that the Shadow DOM can be used in a server-rendered

environment.

23

CHapTeR 1 GeTTinG STaRTeD

Composition Using Slots and Templates


As it happens, the answer to that last question is yes – let me introduce you
to the concept of using slots and templates!

For some time, we (as developers) have been able to use the API to create
encapsulated content or components using the Shadow DOM API, but in
most cases, the content has to be hard-wired. This limitation isn’t great – it
means reusability sucks and that we will be constrained on how we can use
it.

However, we have a more significant issue around server-side

rendering, mainly when using frameworks like React. The Shadow DOM

API doesn’t work as well in these instances, particularly if we need to render


content server-side or to static HTML during the build process. This is
especially important for those visitors who can’t use JavaScript!

To get around (and remove this limitation), we can use slots and

templates – this forms the basis for something called the Declarative Shadow
DOM. Let’s look at what this is as part of our next demo.

USING SLOTS AND TEMPLATES

To use slots with the Shadow DOM api, follow these steps:

1. First, create a new folder on your pC – call it using-slots-

vanilla-js.

2. inside this folder, crack open a new file and add this HTML

markup – there’s a bit to cover, so we’ll do it in blocks, starting with the


head tags:

<!DOCTYPE html>

<html>
<head>

24

CHapTeR 1 GeTTinG STaRTeD

<meta charset="utf-8" />

<title>Simple Template Demo</title>

<script src="main.js" defer></script>

<link rel="stylesheet" type="text/css"

href="styles.css" />

</head>

3. next, immediately below the closing </head> tag, add the first part of the
page markup, which includes our base template:

<body>

<h1>Simple Template Demo</h1>

<p>This line is not in the Shadow DOM</p>

<template id="my-paragraph" shadowrootmode="open">

<style>

p{

color: #000;

background-color: #ccc;

padding: 5px;

}
</style>

<p><slot name="my-text">My default text

</slot></p>

</template>

4. Leave a line blank, then add this call to <my-paragraph>:

<my-paragraph>

<span slot="my-text">This text is in a span

inside the Shadow DOM</span>

</my-paragraph>

25

CHapTeR 1 GeTTinG STaRTeD

5. Leave the next line blank, then add this second call to <my-paragraph>,
this time with different markup within:

<my-paragraph>

<ul slot="my-text">

<li>This is list item 1</li>

<li>A second item in a list!</li>

</ul>

</my-paragraph>

</body>

</html>
6. Save the file as index.html, then close it.

7. Open a new file, then add these styles, and save it as

styles.css – these are primarily for making the result a little

easier to view:

body { width: 500px; margin: 40px auto; border: 1px

solid #000; padding: 10px; }

p { color: #f00; }

8. This next step brings it all together – this is where the magic happens!
Crack open a new file, then add this script:

customElements.define('my-paragraph',

class extends HTMLElement {

constructor() {

super();

const template = document.getElementById('my-

paragraph');

const templateContent = template.content;

this.attachShadow({mode: 'open'}).appendChild(

templateContent.cloneNode(true)

26
CHapTeR 1 GeTTinG STaRTeD

);

);

const slottedSpan = document.querySelector('my-

paragraph span');

console.log(slottedSpan.assignedSlot);

console.log(slottedSpan.slot);

9. Save the file as main.js in the same folder.

10. all that remains is to run the demo – double-click index.html.

if all is well, we should see something akin to that shown in


Figure 1-9.

Figure 1-9. Result of the slots demo

as an aside, you will notice that we render the results of two console.log
statements at the end of the script – a quick check in our browser console
will show first the element inside the slot, followed by the name of the slot.

When using slots, we tend to refer to content inserted as being slotted into
the DOM – you can see what we're slotting in in Figure 1-10.

27

CHapTeR 1 GeTTinG STaRTeD

Figure 1-10. The contents of the slotted element

Although this last demo looks a little different in terms of how we’ve created
the Shadow DOM and attached the element to the host, there are still some
similarities. Look closely, and you can see we’ve used

.appendChild() again, but this time we’ve introduced customElements.

define(); what does that do? To find out, let’s dive back into the code again
and look at it in more detail – this also introduces a new concept for the API
called the Declarative Shadow DOM.

Breaking Apart the Code Changes

Cast your mind back to the previous demos, where we inlined the content in
our component – while this works very well, it can have limited use. It’s
only useful if the content is static, which isn’t likely very often!

Instead, we can use slots and templates to make the components we

host in our Shadow DOM more dynamic. We use similar techniques as

before, but we shift the content from the component to the markup on the
page this time. This might sound a little odd, given we would want content
to be inside the component. Don’t worry; think of it as displaying all markup
in the DOM on the page but using the component to make the content visible
when needed. Let’s dive in and take a look at the code in more detail.

We started by creating the markup for our page – most of this is standard
HTML for calling our styles and script file, followed by the body, which
contains the calls to our component. This is the critical part: the first block
includes the template we use to determine how to render our content on 28

CHapTeR 1 GeTTinG STaRTeD

screen. In all cases, we call our component using <my-paragraph></my-


paragraph> – if we don’t pass any props, it will display a default value of
My default text on the screen. At the same time, we provide some basic
styling

– this we can override, though, as we have set shadowrootmode to open.

notice also that we have a standard <p> tag – this will not be part of the
Shadow DOM api and will help illustrate the differences in styling.

In our case, though, we want to pass specific text, so inside each of the <my-
paragraph>...</my-paragraph> tags, we pass different text. We include text
in <span> tags in the first example, while the second sets a small unordered
list.

This next part is where the magic really happens: in our script file, we set
customElements.define to create my-paragraph, which is the basis for our
component. Inside it, we assign a constructor() method, then set super() to
allow us to access the parent elements of this component (which we define
as HTMLElement). Next, we search for my-paragraph as an ID in our DOM
and assign the content within templateContent. We attach it to an instance of
the Shadow DOM, using appendChild() and cloneNode() – this will render
the content in our Shadow DOM instance.

To finish this component, we use console.log to display the

contents of the assignedSlot (in this case, my-text), followed

by the name of the slot itself, which is my-text. This part illustrates how
everything works in our demo – we would remove this last part

if moving into production!

Before we move on, there is one important point I want to talk about: we’ve
used slots and a template in this last demo, but it forms the basis for
something called the Declarative Shadow DOM.

29

CHapTeR 1 GeTTinG STaRTeD

Using Slots and Templates – A Postscript

Don’t worry if this confuses things: it’s just another way to use the Shadow
DOM! Both work in a similar manner, but using slots and templates has a
crucial advantage over standard Shadow DOM usage: rendering on

the server.

The Declarative Shadow DOM (or DSD) allows us to render content

server-side, which is particularly useful when using frameworks such as


React and Vue. The developers of the API were aware for some time that
using the standard Shadow DOM wouldn’t work so well in this respect, so
they have adapted the API to allow us to render content as static HTML

during build, particularly for those visitors who can’t use JavaScript by
default.
When it comes to using it, the best way to decide which depends on your
project requirements: Do you need to render content server-side? If so, the
DSD is the way forward; if not, you can use the standard Shadow DOM API.
You may decide to use the DSD method throughout, which is perfectly fine
– be aware that, at the time of writing, it’s not yet supported (officially) in
Firefox. You can polyfill it using a function, which is available at
https://developer.chrome.com/articles/declarative-shadow-

dom/#polyfill; this will be enough to display content as expected, but the


experience might be slightly different for Firefox browsers.

if you want to check current browser support, please visit https://

caniuse.com/declarative-shadow-dom.

Let’s move on: we’ve worked through a couple of examples of using

the Shadow DOM API (as well as its cousin, the Declarative Shadow

DOM). There is one crucial subject we’ve yet to explore: How do we style
our content or components? It might initially seem odd to ask this 30

CHapTeR 1 GeTTinG STaRTeD

question, as you would rightly expect to use standard styles. However, all is
not as it would seem – there are some quirks, so let’s dive in and look in
more detail.

Applying Styles – What's Different?

Earlier in this chapter, I mentioned that styling could present

some challenges when applying changes to the content within the

Shadow DOM.

Cast your mind back to our first demo, where I used JavaScript to apply
styles – while this works well, using it consumes more resources and makes
our page heavier. It might not be a problem with a small change like our
demo, but the cumulative effect could adversely impact our site if we use it
multiple times.

This issue is one of the “challenges” we face when using the API – we must
be mindful of our approach when styling our content. Let’s take a look at a
few more quirks relating to styling:

• We can use standard <style> tags, but we have to

inline these as a string of styles and aim to use them as

part of creating templates.

• The first one is the use of ({ mode: open }) or

({ mode: closed }) when using .shadowAttach() –

the former will leave the component or content open

to receiving styles from outside the Shadow DOM,

while the latter will keep the styles from leaking into

our content. As an aside, it’s considered better practice

to use mode: closed – while this will limit styling, it

also reduces the risk of hacking attacks. It may not be

possible to use it, but it is something that we should

keep in mind!

31

CHapTeR 1 GeTTinG STaRTeD

• You can still use CSS-in-JS to style components, but

remember that we can override the styles using the


:root tag, particularly if you’re using CSS variables.

• Historically, we could only modify styles in a custom

element (wrapped inside the Shadow DOM/Host)

by using custom CSS properties. This method might

work for a limited number of properties, but for many

sites, this will become tedious to set up and manage.

However, browsers have introduced two shadow part

tags we can use, which are ::part and ::theme. We’ll

explore using them in more detail in the next chapter,

but for now, it’s important to note that these can

override properties in the encapsulated component or

content, which makes them more versatile.

• If we still need to use custom CSS properties, I

recommend keeping the number as small as possible.

Custom properties such as color: var(--black) can

filter down automatically into the Shadow DOM and

potentially override content, which could lead to some

odd effects!

The developer Joan Llenas Masó has written an interesting article

on Dev about how to create a design theme for use with the Shadow
DOM and web components – it’s available at https://dev.to/

joanllenas/web-components-styles-piercing-shadow-

dom-24n0.

32

CHapTeR 1 GeTTinG STaRTeD

An exciting selection of options, and hopefully (to quote a phrase)


something to give you food for thought! The ultimate choice will depend on
what you already use and what works for your environment – each will have
its constraints, which will determine suitability for your application.

Summary

Over the last few years, tools such as React have become popular when
developing online applications – so much so that some may wonder why we
did anything different! However, some people are beginning to wonder if
this is still valid and whether there is an alternative way to achieve the same
effect.

One of those tools we could use as this alternative is the Shadow DOM

API – we began first by exploring the different DOM types, such as the
Light DOM, real DOM, and, of course, the Shadow DOM itself. We then
created a couple of examples that use the Shadow DOM API – one using
vanilla JavaScript and the other in React.

Next up, we moved on to covering the key elements of the API before taking
a quick look at browser support and some of the more practical
considerations of using this API. We then finished with a dive into a
variation of the API called the Declarative Shadow DOM that uses slots and
templates; we explored how this could help with rendering content on the
server when using frameworks such as Svelte and React.

Phew – we’ve covered a lot in the first few pages of this book: let’s move on
to the next part of our adventure! We’ve talked about how we can use the
Shadow DOM API to create what will effectively be web components –

let’s put that to practice and go through the end-to-end process of creating
such a component in the next chapter. We’ve got a lot to cover, so stay with
me, and I will reveal everything....

33

CHAPTER 2

Creating Components

Over the last few pages, we started to explore the Shadow DOM API and
how we might use it to encapsulate code into a feature or component. We
created a couple of simple examples and covered some of the terms and
functions available for use in this API.

This is all good, but what about using it in a practical context? The best way
to do this is by writing components (or features) – while this book is about
the Shadow DOM API, it forms the basis for creating web components, such
as those offered in the Lit web library at https://lit.

dev/. With that in mind, I will take you through two examples of practical
use – the first is an alert dialog, and the second is a toggle switch.

We’ll see how to create the code and learn how to test these

components and how to package them for reuse by others in your team or
further afield. Let’s begin with the simpler of the two, by creating the alert
dialog component.

For this chapter, our work will be in a project folder, which I’ve named
creating-component. If you see any references to the project

folder, please assume it is this folder unless otherwise stated!

© Alex Libby 2024

35
A. Libby, Beginning Shadow DOM API, Apress Pocket Guides,

https://doi.org/10.1007/979-8-8688-0249-2_2

ChapTer 2 CreaTIng ComponenTs

Creating an Alert Dialog

For the first of our two projects, I will create a standard alert dialog – this is
the type of component you would see if you needed to convey a message to
the end user.

We will start with the markup and then add styling; we will work on the tests
separately once we complete the second project later in this chapter.

Let’s begin with setting up that markup and creating the core part of the
component.

Constructing the Markup

For the first part of this project, we’ll start not with creating the component
itself but the markup – this will give you an idea of how it will look, the
properties we need to pass, and the information we can display in the alert.

As an aside, when you look at the code, you may notice that I have used a
different format than earlier examples. Don’t worry – this is perfectly fine:
the API is very versatile and will work with pretty much any JavaScript-
based code if we include the proper functions such as customElements.

define() or shadowRoot.appendChild(). It does mean that once you get to


grips with using the API, you are free to find a pattern or format that suits
your needs or projects!

BUILDING THE ALERT

To create the alert, follow these steps:

1. First, create a new folder called alert-component inside our


project folder.

2. Insite the alert component folder, go ahead and open a new file, then add
this markup – we’ll do it in two halves, starting with

the opening tag and <head>…</head> block:

36

ChapTer 2 CreaTIng ComponenTs

<!DOCTYPE html>

<html lang="en">

<head>

<title>A Simple Component using Shadow DOM

API</title>

<meta charset="utf-8" />

<script src="script.js" defer></script>

<link rel="stylesheet" type="text/css"

href="styles.css" />

<link

href="https://fonts.googleapis.com/css2?family

=Roboto:wght@400;900&display=swap"

rel="stylesheet"

/>

</head>
3. next, leave a line blank, then add the second half of the markup as shown:

<body>

<span>

<my-alert>This is an alert</my-alert>

</span>

<span>

<my-alert class="info">This is an information

alert</my-alert>

</span>

<span>

<my-alert class="warn">This is an warning

alert</my-alert>

</span>

</body>

</html>

37

ChapTer 2 CreaTIng ComponenTs

4. save the file as index.html, then close it.

5. next, open a new file, then add this script block – we have

a fair chunk of code, so as before, we’ll do it in two halves,


starting with defining an opening constant and assigning

content to it:

const template = document.createElement("template");

template.innerHTML = `

<style>

:host {

display: block;

padding: 10px;

border: 1px solid hsl(206, 74%, 54%);

border-radius: 4px;

background: hsl(206, 74%, 90%);

</style>

<div>

<slot></slot>

</div>

`;

6. This next part is where the magic happens – this turns our

component into one using the shadow Dom and therefore

usable by any framework:


customElements.define(

"my-alert",

class CustomAlert extends HTMLElement {

static is = "my-alert";

constructor() {

super();

this.attachShadow({ mode: "open" });

38

ChapTer 2 CreaTIng ComponenTs

this.shadowRoot.appendChild(template.content.

cloneNode(true));

connectedCallback() {

if (!this.hasAttribute("role")) {

this.setAttribute("role", "alert");

);

7. save the file as script.js in the same alert component folder, then close it.
8. If we ran our demo now, we’d see an alert, but it won’t look like one! To
fix that, we need to add some styles – go ahead and

add this code to a new file, saving it as styles.css in the same

alert component folder:

my-alert:not(:defined) { display: none; }

my-alert { display: flex; padding: 30px 60px; font-

family: "Roboto", sans-serif; }

.info { background-color: #fff5af; border: 1px solid

#f8af34; color: #000000; }

.warn { background-color: #e94747; border: 1px solid

#891111; color: #000000; }

span { display: flex; margin: 20px 0px; display: flex;

flex-direction: column; align-items: center; }

9. save and close the stylesheet – go ahead and open index.html

in a browser. If all is well, we should see something similar to

Figure 2-1.

39
ChapTer 2 CreaTIng ComponenTs

Figure 2-1. Demo of the finished my-alert component

Perfect – we have a component that I hope is more useful than what we


created in Chapter 1! Most of the code in this demo is standard HTML, CSS,
and JavaScript, but it does use some interesting techniques. We covered
some of them in the previous chapter, so let’s dive in and take a closer look.

Understanding What Happened

Alerts are never something you want to see on a website – I think it has
something to do with striking a little uncertainty in one’s mind about
whether a process has completed successfully. However, the reality is that
we need to display alerts occasionally – we must have an appropriate
mechanism in place to deal with them.
For this first project, we constructed a simple example – we can use it to
display standard, informational, or (heaven forbid) warning alerts. We began
with creating the markup for the demo, which we use to test that the 40

ChapTer 2 CreaTIng ComponenTs

alert works; in this, we link to the component using a script tag and apply
some basic styling. In the second part of the markup, we render three
instances of the my-alert component – notice that to differentiate the
different types, we must pass in different classes. It means you are free to
generate suitable rules in your stylesheet, and the component will render any
it finds on the page.

The trick to making this work is in the component itself – we use

JavaScript to create an element called template, into which we add a basic


placeholder containing a <slot> tag. This slot tag is just a placeholder for any
markup we pass – it will render what it sees! We do, though, have a
:host rule in the <style>...</style> block – this is applied by default if we
don’t pass in a class name. You can see what this looks like in the console
log in Figure 2-2.

Figure 2-2. The compiled alert component, as shown in the console log

41

ChapTer 2 CreaTIng ComponenTs

In the second part of the component, we define the customElement.

We give it a name of my-alert and create a class CustomAlert that extends


HTMLElement. In this, we set static is my-alert to ensure that the
component’s internal properties do not change across different instances,
save for when we alter styling. We then set this.attachShadow({open}) to
create our Shadow DOM instance and override styles; we follow this by
appending the content of template as a duplicate Node.js.

This next part is an interesting feature we have not yet used in our demos –
the connectedCallback() function. This function is called every time we add
an instance of the component to the Shadow DOM and is one we should
implement as a separate custom callback function, not in the constructor, as
we have done here. In this instance, we check to make sure that the
component has a role attribute set – if one is not set, then we add it to the
markup.

To round things off, we add some styling to match up with the classes we
created earlier – .info and .warn; these override the initial styles set in the
component, which we prove by running the demo in our browser to verify
the results.

Okay – let’s crack on: we have a second component to construct. For this
next one, I’m going to take a different tack, which is to reuse a third-party
example; it shows off some new features of the API that we have yet to
explore. The component in question is a toggle switch – let’s … well, do as
it says … and switch to exploring what is involved in more detail (yes, pun
very much intended!) as part of the next demo.
Creating a Toggle Switch

Our next project is a component that you will find on websites everywhere,
probably more on a settings page than on the first page of a site! I’m talking
about the humble toggle switch – from a developer’s perspective, we might
see it used to switch between light and dark themes, while end users might
use it to set their preferences for things such as receiving marketing emails.

42

ChapTer 2 CreaTIng ComponenTs

We could develop our next component from the ground up but will

take a different approach this time. Instead, we’re going to work through a
simplified version of an existing component by Timothy Foster, available on
GitHub at https://github.com/Auroratide/toggle-switch/tree/master.

It gives us an excellent opportunity to see how others might create a


component that uses the Shadow DOM API (and customElements). For

this demo, I simplified the code in the component, merged some of the
dependencies, and reformatted it to use my demo file. Let’s look at the code
we need to use to create this component in more detail.

Just a small heads-up – given the number of steps involved, we’ll go through
this demo in two parts. The first will deal with creating the component, with
the demo coming in the second part of this demo.

CREATING A SWITCH COMPONENT

To build our fancy switch component, follow these steps:

1. First, go ahead and create a folder called toggle-switch

inside our project folder – inside this, create a folder called lib.

2. next, crack open a new file, then add this code – we have a lot, so as
before, we’ll do it in sections, beginning with some const
definitions:

const CHANGED = "toggle-switch:change";

const CHECKED_ATTR = "checked";

const DISABLED_ATTR = "disabled";

const changeEvent = (checked) =>

new CustomEvent(CHANGED, {

detail: { checked },

});

43

ChapTer 2 CreaTIng ComponenTs

3. next, leave a line blank, then add this block – this creates the styling (and
a placeholder) for our switch component:

const template = document.createElement("template");

template.innerHTML = `

<style>

:host {

display: inline-block;

width: 2em;

height: 1em;

cursor: pointer;

}
span {

box-sizing: border-box;

display: inline-block;

line-height: 1;

[part="track"] {

width: 100%;

height: 100%;

background-color: #dddddd;

text-align: left;

transition: all 0.256s;

[part="slider"] {

width: 50%;

height: 100%;

background-color: #777777;

transition: all 0.256s;

vertical-align: text-top;

44
ChapTer 2 CreaTIng ComponenTs

:host([checked]) [part="slider"] {

transform: translateX(100%);

:host([disabled]) {

cursor: not-allowed;

opacity: 0.5;

</style>

<span part="track">

<span part="slider"></span>

</span>

`;

4. Leave a link blank after the template block, then add this

code – this is the core part of the switch component, starting

with the opening constructor:

class ToggleSwitch extends HTMLElement {

static elementName = "toggle-switch";

// Currently, only Chrome adequately supports this

part of the spec


static formAssociated = true;

static get observedAttributes() {

return [CHECKED_ATTR];

constructor() {

super();

this.attachShadow({ mode: "open" }).appendChild(

template.content.cloneNode(true)

);

45

ChapTer 2 CreaTIng ComponenTs

5. This next block takes care of checking for the presence of role and tab
index, then adds one of each if it’s not already present.

at the same time, we also add two event handlers:

connectedCallback() {

if (!this.hasAttribute("role")) {

this.setAttribute("role", "switch");

if (!this.hasAttribute("tabindex")) {

this.setAttribute("tabindex", "0");
}

this._updateChecked(false);

this.addEventListener("click", this.toggle);

this.addEventListener("keydown", this._onKeyDown);

6. When the component is dismounted, we should tidy up after

ourselves – this function takes care of removing the component

from the document:

disconnectedCallback() {

this.removeEventListener("click", this.toggle);

this.removeEventListener("keydown", this._

onKeyDown);

attributeChangedCallback(name, oldValue, newValue) {

if (name === CHECKED_ATTR) {

this._updateChecked(true);

46

ChapTer 2 CreaTIng ComponenTs


7. as this is a component, we also need to set getters and

setters – this takes care of working out if the button is checked

(“switched on”) or unchecked (“switched off”). We also do

something similar for the disabled state at the same time:

get checked() {

return this.hasAttribute(CHECKED_ATTR);

set checked(value) {

this.toggleAttribute(CHECKED_ATTR, value);

get disabled() {

return this.hasAttribute(DISABLED_ATTR);

set disabled(value) {

this.toggleAttribute(DISABLED_ATTR, value);

toggle = () => {

if (!this.disabled) {

this.checked = !this.checked;

}
};

_onKeyDown = (e) => {

switch (e.key) {

case " ":

case "Enter":

e.preventDefault();

this.toggle();

break;

47

ChapTer 2 CreaTIng ComponenTs

default:

break;

};

_updateChecked = (dispatch = false) => {

this.setAttribute("aria-checked", this.checked.

toString());

if (dispatch) this.dispatchEvent(changeEvent(this.

checked));

};
}

8. This last part ties it all together – this is our call to define the component
based on the class we’ve created:

window.customElements.define(ToggleSwitch.elementName,

ToggleSwitch);

9. save the file as toggle-switch.js in the \toggle-switch\lib

folder, then close all files – we will create a demo for our new

component shortly in the next exercise.

Phew – there’s a lot of interesting stuff going on in this demo! If you look
carefully, you’ll see we’ve already used some of the elements we’ve touched
on, such as customElements.define() or the attachShadow()

method. We’ve used some additional functions for the first time in this
demo, so let’s dive in and look at the code in more detail to see how it all
hangs together.

48

ChapTer 2 CreaTIng ComponenTs

Breaking Apart the Changes

At first glance, you’ll be forgiven for thinking that the last demo seemed to
have a lot of code for a simple toggle switch! While this may seem true, a
good chunk of it is styling, and our component uses some keywords we’ve
already used earlier in this book.

So – how did we build our component? We started by defining three

variables, namely, CHECKED_ATTR, DISABLED_ATTR, and CHANGED


– these store the appropriate state of the switch. We then added an event
handler to determine if the change event has been triggered and to allow us
to render the result to the end user.

The next part is critical to the component – we create the template for our
toggle switch. For this, we use standard JavaScript to create and initialize an
element template with the appropriate HTML. While this is standard, the
magic happens in the HTML and CSS we use – you will notice styles such
as :host and [part="..."] in the code.

The :host rule allows us to override these styles with external CSS if any is
provided; if not, we use the styles defined in this template. When it comes to
using the part property, think of this as a way of applying a class to a specific
element, but one that sits inside a Shadow DOM-enabled component. If you
look a little lower down, you will see a simple fragment of HTML, starting
with <span part="track">... – if we specify a rule like

[part="slider"], this will override the one provided in the template.

Moving on, we come to the JavaScript block for our component – here, we
set up a class called ToggleSwitch, which extends the HTMLElement; this is
a typical arrangement for creating web components that use the Shadow
DOM API. Inside it, we add a static getter to return the CHECKED_

ATTR status of our component, as well as a constructor and the now-familiar


this.attachShadow to create and initialize an instance of the Shadow DOM,
using our template.

49

ChapTer 2 CreaTIng ComponenTs

Next up, we then add a series of functions and event handlers – these add
role tags if they are not present (connectedCallback()) or remove click or
keydown event handlers if the component is removed from the page
(disconnectedCallback()). The remainder of the functions take care of
getting or setting the state of our switch’s attributes and enabling or disabling
the component. We round off the demo with the now-familiar
customElements.define() method, which creates our custom element as a
web component.
We now have a toggle switch component in place – the question is,

does it all work? There is only one way to find out: write some unit tests to
ensure it works as expected. However, that takes time – we can at least
perform a visual check in a simple demo: let’s dive in and create something
to show off our new component.

Constructing the Demo

Testing a switch is a straightforward affair – after all, there isn’t much we


can test, save for whether it is enabled or disabled and whether the results
change based on the state of the switch! That said, a visual test is always
helpful – we can prove it looks and works as expected before writing any
unit tests to give a more scientific answer.

To prove whether our new switch works, I’ve simplified a copy of

the demo from the original author of the toggle switch component – this
displays some text on-screen, which flips between two different styles.

50

ChapTer 2 CreaTIng ComponenTs

CONSTRUCTING THE DEMO

To create a demo for our component, follow these steps:

1. First, crack open a new file in your editor – go ahead and add this markup,
which we will do in two stages, beginning with the

opening tags and <head> block:

<!DOCTYPE html>

<html lang="en">

<head>
<title>Toggle-Switch Element</title>

<link

href="https://fonts.googleapis.com/css2?family=

Henny+Penny:wght@400;700&display=swap"

rel="stylesheet"

/>

<link rel="stylesheet" href="demo.css" />

<script src="lib/toggle-switch.js" defer></script>

<script src="demo.js" defer></script>

</head>

2. next, we need to add the main markup for our demo – for this,

add the following code immediately below the closing </head> tag of the
previous step:

<body>

<main>

<h1>Toggle-Switch Component Demo</h1>

<section id="style-demo" class="demo">

<figure>

<p class="switch-container">

<label for="fancy-switch">

Fancy Switch
51

ChapTer 2 CreaTIng ComponenTs

</label>

<toggle-switch id="fancy-switch"

class="fancy"></toggle-switch>

</p>

<p class="result">Watch the switch flip the

styling!</p>

</figure>

</section>

</main>

</body>

</html>

3. save the file as index.html and close the file. next, we need

to create a script file to satisfy the call for demo.js in step 1 –

go ahead and create that file at the root of the toggle-

switch folder.

4. In the file, add the following code – this will change the font used in the
demo:

document

.querySelector("#style-demo toggle-switch")
.addEventListener("toggle-switch:change", (e) => {

const result = document.querySelector("#style-demo

.result");

result.classList[e.detail.checked ? "add" :

"remove"]("styleish");

});

5. Lastly, we should add some styling – for this, we’ll add a mix of rules to
make our demo look presentable, but others will be

to show off the new switch component. We’ll be adding quite

a few rules, so let’s start with the ones needed to set up the

essential elements of the demo:

52

ChapTer 2 CreaTIng ComponenTs

*,

*::before,

*::after { box-sizing: border-box; margin-top: 0; }

body { font-family: "Roboto", sans-serif; padding: 8px;

background: #ededed; }

main { max-width: 800px; margin: auto; }

h1 { font-size: 48px; font-weight: bold; text-align:

center; }
.demo { margin-bottom: 96px; }

6. This next block takes care of styling the button part of our new toggle
switch component:

button,

::part(button) {

font-family: Arial, Helvetica, sans-serif;

font-size: 12px;

border-radius: 4px;

border: none;

padding: 10px 18px;

color: #ffffff;

text-transform: uppercase;

letter-spacing: 1px;

box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.25);

cursor: pointer;

background: #468fdd;

button:hover,

::part(button):hover {

box-shadow: 0 4px 4px -2px rgba(0, 0, 0, 0.25),

0 0 0 48px rgba(0, 0, 0, 0.2) inset;


}

53

ChapTer 2 CreaTIng ComponenTs

button:active,

::part(button):active,

button[disabled] {

box-shadow: 0 0 0 48px rgba(0, 0, 0, 0.3) inset;

button[disabled] {

pointer-events: none;

opacity: 0.5;

7. This last batch looks after the changes effected when enabling or disabling
the switch:

figure {

font-size: 24px;

background-color: #ffffff;

padding: 16px;

border-radius: 4px;

border: 1px solid #468fdd;

min-height: 150px;
}

figure p { text-align: center; }

figure p:last-child { margin-bottom: 0; }

.switch-container { display: flex; align-items: center;

justify-content: center; }

label { padding-right: 4px; }

label::after { content: ": "; }

.styleish { font-family: "Henny Penny", cursive; }

.fancy { height: 18px; }

54

ChapTer 2 CreaTIng ComponenTs

.fancy::part(track) { padding: 2px; border-radius:

16px; background-color: #ababab; }

.fancy::part(slider) { border-radius: 16px; background-

color: #ffffff; box-shadow: 1px 1px 2px hsla(0, 0%, 0%,

0.25); }
.fancy[checked]::part(track) { background-color:

#468fdd; }

8. save the file as demo.css in the toggle-switch folder. go

ahead and preview the results in a browser – if all is well, we

should see something akin to that shown in Figure 2-3. Try switching it back
and forth to see what happens!

Figure 2-3. Enabling the new toggle switch component

Phew – there’s a lot of code there, but the reality is that much of it is
standard HTML and CSS. There are, however, some interesting concepts in
this demo, particularly within the CSS: let’s take a closer look at the styles
we’ve created to understand how they work in more detail.

Breaking Apart the Code

So what did we create in our last demo? We began by adding some basic
markup to host our switch component – if you look closely, you can see
standard HTML tags such as the usual <head>, a link to an (optional)
Google font, and CSS used in our demo. Nothing outrageous there!

55

ChapTer 2 CreaTIng ComponenTs

Where things start to get interesting is later in the <body> of the markup.
Notice that when we call the <toggle-switch> component, we don’t use the
typical self-closing tags that we might otherwise use when working with
frameworks such as React. Instead, we must specify the full tags – not doing
so can lead to some odd effects when rendering the component on the page.

The next point of interest is in demo.js – here, we added an event handler


using standard JavaScript. We target the toggle-switch element inside the
#style-demo block before finding the instance of #style-demo
.result (which is the text). We assign it to a variable result, which we then
use to determine if we add or remove the stylelish class based on whether we
check or uncheck the box.

Moving on, the last area of note is in the CSS rules we created – and the use
of ::part. According to MDN, this “CSS pseudo-element represents any
element within a shadow tree with a matching part attribute.” In other words,
if we have a part="..." tag on an element, we can style it using ::part.

Take a look back at the markup we created in the component; here’s a


reminder of it:

<span part="track">

<span part="slider"></span>

</span>

We can treat part="..." as a standard CSS class or ID; the only real difference
is that we can only use this tag in a Shadow DOM component. It means that
we can use rules such as

button,

::part(button) {

font-family: "Roboto", sans-serif;

...

56

ChapTer 2 CreaTIng ComponenTs

to target the appropriate part of the component. Here we apply the Roboto
(or sans-serif) font to both standard buttons (using button) and those in the
Shadow DOM (with the ::part attribute).
Okay – let’s crack on: we have reached a significant milestone. Over the last
few pages, we created two components that use the Shadow DOM API and
can form the basis for something more detailed in a future project. The next
task is to test these components properly – in a way, we’ve already done a
visual test in a browser, but we still need to do unit tests. Ordinarily, I would
say that we could use any of a dozen different testing suites, such as Testing-
Library or Jest, but as always, things are not as straightforward as they
seem….

Testing the Components

Although plenty of testing suites are available, such as Testing-Library or


Cypress – and I’m sure you will have a preferred choice – here’s the rub:
many of these libraries do not support the Shadow DOM API!

Do not worry, though, as there is one framework that does – it’s from the
Open Web Components site at https://open-wc.org/docs/testing/

testing-package/. It’s not a package that is as well known, but it uses a very
similar syntax to Jest, so it should be easy to learn.

For our next demo, I’ve elected to use the alert component as our

working example – we’ll step through setting up the test framework and then
write some simple tests to give you a flavor of how we might test our
component. Let’s dive in and look at what’s involved, starting with installing
the testing framework.

This should be a formality as we’ve already used it, but make sure you have
node.js and npm installed – the site doesn’t state which

version(s) it supports, but anything relatively recent in the last two to three
years should work.

57

ChapTer 2 CreaTIng ComponenTs

DEMO: TESTING THE COMPONENT


To test our alert component, follow these steps:

1. First, create a new folder called testing-components – this

needs to be inside the creating-component folder, at the same

level as the alert-components folder.

This is important, as we will import the alert component directly from the
folder used in the previous demo to save copying it over.

2. next, go ahead and extract a copy of the package.json from

the code download and put it into the testing-

components folder.

3. Fire up a command prompt, then make sure the working folder

is set to /creating-component/testing-components.

4. enter npm install to install dependencies at the prompt, and

press enter.

5. With the main testing framework now in place, let’s add the

tests for our component. go ahead and add a folder called __

tests__ inside the testing-components folder, so you should

end up with \testing-components\__tests__.

6. Inside this folder, open a new file, then add this code:

import { expect, fixture, html } from "@open-wc/

testing";

describe("my-test", () => {
it("is accessible", async () => {

const el = await fixture(html` <my-alert>This is a

test</my-alert> `);

58

ChapTer 2 CreaTIng ComponenTs

expect(el).to.be.accessible();

});

it("does not show custom text", async () => {

const el = await fixture(html` <my-alert

class="info"></my-alert> `);

expect(el.innerHTML).to.equal("");

});

it("renders with a class of info", async () => {

const el = await fixture(html` <my-alert

class="info"></my-alert> `);

expect(el).to.have.class("info");

});

});

7. save the file as alert.test.js – you can keep it open.

8. switch to a command prompt, then enter npm run test


and press enter to run the tests – if all is well, we should get

this output:

$ npm run test

> [email protected] test

> web-test-runner "__test__/**/*.test.js"

--node-resolve

(node:23580) [DEP0040] DeprecationWarning: Thèpunycodè module is


deprecated. Please use a userland alternative instead.

(Usènode --trace-deprecation ...` to show where the

warning was created)

__test__\alert.test.js:

Browser logs:

59

ChapTer 2 CreaTIng ComponenTs

Lit is in dev mode. Not recommended for

production! See https://lit.dev/msg/dev-mode for more

information.

Chrome: |█████████████████

█████████████| 1/1 test files | 3

passed, 0 failed

Finished running tests in 2.9s, all tests passed!


Excellent! We have the basis in place for our tests – while we may have
added simple checks, we can at least say we have a working means to unit
test our component. We can add to or alter the tests – what we have written
is just a starting point! There are, however, a few interesting points of note,
so let’s pause for a moment to explore the code in more detail.

Understanding What Happened

As a developer, I’m sure you can appreciate that testing our work is an
essential part of the development process – we need confirmation that what
we create works as expected, be that a single atom component, all the way
through to a complex feature!

Ordinarily, we would use something like Jest, Cypress, or even Testing-


Library, but – as I mentioned just now – none of them support the Shadow
DOM, or might if you were using a framework and not pure JavaScript!

So – how could we test our component? We turned to a different Open-WC


package from the Open Web Components team. It’s probably not as well
known, but it works in the same way as Jest, so it should be easy to pick up.

To prove this, we installed it into an empty project using Vite – Open-WC is


npm based, so using Vite makes it easier to install.

We then added a __tests__ folder before writing three tests: the

first assertion to validate that the component is accessible, the second to 60

ChapTer 2 CreaTIng ComponenTs

confirm we don’t show any custom text (but render the default fallback text
instead), and the last to validate that we are using the info class to style our
component. Notice how we import and use the fixture method into each test
– this renders and inserts a piece of HTML into the DOM so that we can
then test our component with confidence. By default, this method is done
asynchronously (hence the await) – we can ensure the component is
rendered correctly before completing each expect statement.

With the tests completed, we then ran the web-test-runner, which


we installed as part of the initial project setup (it was preset to install when
we ran npm install). The runner ran each test before outputting the results
from our test file. To finish, it displays a statement to say whether everything
has passed or if there are any failures – hopefully, you got the former when
running your demo!

Before we move on, though, there are a couple of interesting points we


should explore that you may see or get:

• You may (or may not) get a deprecation notice for

punycode, depending on which version of Node.js you

use. This warning is a known issue, which you can

ignore – punycode has been deprecated by Node.js and

is now a runtime dependency for Open-WC. It might

add a little noise but will not affect your testing.

• You may also see a comment starting with “Lit is in dev

mode. Not recommended…”. This is to be expected,

as Open-WC was written using the Lit framework; Lit

works with all manner of different frameworks.

61

ChapTer 2 CreaTIng ComponenTs

If you want to learn more about the testing project from open Web

Components, you can see the documentation on the main site at

https://open-wc.org/docs/testing/testing-package/.

Let’s move on: we have two basic components in place and have
worked through testing one as an example. We could use the component as
is, but that could get a little messy – we wouldn’t get the full benefits of
using it if we don’t wrap it up as a package!

This is where npm comes into its own – it provides a means to wrap our
component into something that makes it easy to reuse or available for others
to download and use via the npm manager. It’s easy enough to set up,
although it requires quite a few steps – let’s dive in and look at how in more
detail.

Packaging the Component for Release

With our components now created, we can use them as they are, but –

what about publishing them as a package to npm? Running them as they are
might be sufficient for your needs, but if we publish them, we can make
them available for others to use – either within your company or externally.

The publishing process is straightforward – there are three steps

involved:

• Upload the source code to GitHub.

• Add a package.json with details of our component so

npm can find and install it – we’ll use the alert dialog as

the basis for this demo.

• Run npm’s publish command to push it up to its

repository.

62

ChapTer 2 CreaTIng ComponenTs


It sounds complex, but there isn’t anything complicated – there are a couple
of things we need to do first before we can crack on with the process. They
are as follows:

• We need an area to upload the component; for this

demo, I will assume you have one available in GitHub

and sufficient access to log in and upload the code. If

you want to use a site such as GitLab, this should work

OK as long as it can publish to npm.

• You will need Git installed for your platform, along

with Git Bash – if you don’t have either installed,

comprehensive instructions are available on the

Git website at https://git-scm.com/book/en/v2/

Getting-Started-Installing-Git.

• You will also need an account on npm – if you don’t

already have one, head over to www.npmjs.com/signup

to complete the process. I strongly recommend you

complete 2FA, as this may otherwise lead to npm

blocking you, even though you enter the correct

password.

note – you must verify the account with npm before using it, so

make sure your email address is valid! You will get a 403 error when
running the demo if you don’t, as the account will be unverified.
Okay – let’s begin the process by creating a package.json file for our
component package.

63

ChapTer 2 CreaTIng ComponenTs

PART 1: PACKAGING THE COMPONENT

1. First, go ahead and navigate to the alert-dialog folder we

created earlier. We will create the package to publish from this

folder, but if you want to create a separate folder, please feel

free to do so!

2. Inside this folder, open a new git Bash session – make sure the session
shows the working folder as your component folder.

If you’re using Windows, right-click and select git Bash here to do this step
(it may be under the show more properties menu if using

Windows 11). This method means you will be in the correct folder

when the git Bash session window appears.

3. enter npm init -y at the prompt to create a basic package.

json file, then press enter. once it has confirmed creation,

open it in an editor and amend it so it looks like this, replacing XXXX with
your username for npm:

"author": "Alex Libby",

"name": "@XXXXX/alert-component",
"version": "0.1.0",

"type": "module",

"description": "An Alert Dialog Web Component for my Shadow DOM API
book",

"main": "script.js",

"module": " script.js",

"scripts": {

"test": "echo \"Error: no test specified\" && exit 1",

64

ChapTer 2 CreaTIng ComponenTs

"dev": "vite",

"build": "vite build",

"serve": "vite preview"

},

"devDependencies": {

"vite": "^5.0.4"

},

"repository": {

"type": "git",

"url": "git+https://github.com/shadowdombook/alert-

dialog.git"
},

"license": "Apache-2.0",

"bugs": {

"url": "https://github.com/shadowdombook/alert-

dialog/issues"

},

"homepage": "https://github.com/shadowdombook/alert-dialog#readme"

4. To create a demo for our component, we will install Vite and use it to
generate a basic site structure. We’ve already added vite

as a dependency, so at the prompt, enter npm install and

press enter to install it.

5. Vite needs to have an index.html file – if you’ve done it in

the same folder as the original demo, we already have what

we need. switch to a command prompt, then make sure the

working folder points to the alert-component folder. enter npm

run dev to fire up the Vite dev server at the prompt.

65
ChapTer 2 CreaTIng ComponenTs

6. Browse to http://localhost:5173/ to view the index file

we just created – if all is well, we should see something akin to

that shown in Figure 2-4.

Figure 2-4. The original demo, now running under Vite

Do not be alarmed if you don’t see any difference from the original version
of this demo – that is to be expected! Vite needs an index.html file to work
correctly –

rather than creating afresh, we can reuse the one we built earlier in this
chapter.

We’re making good progress, but we still have more to go! Go and have a
breather, get a drink, and let’s continue with the next part of the demo when
you’re ready.

66

ChapTer 2 CreaTIng ComponenTs

The next task is to push our code into a repository from which we can
publish to npm. I used GitHub for this demo; if you don’t already have a
suitable repository, you can sign up at https://github.com/signup. You will
need to create a repository area then; please make note of the details you use
so you can reference them later! As the number of files we need to push is
minimal, we can simply drag and drop all of them (excluding the
node_modules folder) via the upload facility at https://github.com/XXX/

YYY/upload/main, where XXX is the name of your account and YYY is the
name of your repository.

I have not gone into too much detail here, as I assume you will be familiar
with the basics of uploading code to git – you can do it

manually via drag and drop, from the command line, or via a subversion
application. any method is fine: go with whichever you prefer!

Assuming you are successful, then let’s continue with the next part of the
process.

PART 2: PUBLISHING THE COMPONENT

publishing to npm is easy – we only need to run a few commands to


complete the process:

1. Crack open a command prompt or revert to the one from


the previous demo, then make sure we’re still in the same

component folder.

2. at the prompt, enter npm login -access=public, then

press enter.

3. npm will prompt you for your username, password, and email

address – go ahead and enter the details you created before

the start of the previous exercise.

67

ChapTer 2 CreaTIng ComponenTs

4. once done, enter npm publish –access=public at the

prompt, and press enter – if all is well, you will see something

similar to this appear:

npm notice

npm notice @shadowdombook/alert- [email protected]

npm notice === Tarball Contents ===

npm notice 15B README.md

npm notice 675B index.html

npm notice 738B package.json

npm notice 734B script.js

npm notice 506B styles.css


npm notice === Tarball Details ===

npm notice name: @shadowdombook/alert-

component

npm notice version: 0.1.0-alpha

npm notice filename: @shadowdombook/alert-

component-0.1.0- alpha.tgz

npm notice package size: 1.4 kB

npm notice unpacked size: 2.7 kB

npm notice shasum: a1eeaa446b703c5c1f1a8a715c745904803874cf

npm notice integrity: sha512-

nDr91vxi1BmVO[...]BoooaNydIVfaA==

npm notice total files: 5

npm notice

npm notice Publishing to https://registry.npmjs.org/

+ @shadowdombook/[email protected]

If you have 2Fa enabled, you may be prompted to supply a token –

you can do this using the same command as before but append

-otp=<your code> at the end.

68
ChapTer 2 CreaTIng ComponenTs

5. If the publishing process was successful, we should see it

published on npm at www.npmjs.com/package/XXX, where XXX is the


name of your package. It will look similar to the

version I’ve created, which you can see in Figure 2-5.

Figure 2-5. The newly published component on npm's registry 69


ChapTer 2 CreaTIng ComponenTs

At this point, we’re free to test it – we can use a service such as Skypack,
available from https://skypack.dev, which allows us to load optimized npm
packages with no build tools. It would be a matter of replacing the src
location of our script in the demo to point to a Skypack one, and …

well, wait! It takes Skypack a few moments to build the package, but with a
couple of refreshes, we should see the site run using the new package.

This brings us nicely to the end of the creating and publishing process; this is
now available for anyone to use. Throughout this, we’ve covered some
critical tasks in the last couple of demos, so while we catch our breath, let’s
pause for a bit to review the code changes in more detail.

Breaking Apart the Code Changes

Publishing and packaging a component for distribution is something of a


double-edged sword. Some will be proud to release a new component or
update an existing one. But, at the same time, there’s always that question:
How will people take it? Will it land well, or (heaven forbid) … will it
bomb?

Leaving emotions aside, packaging and publishing our process

requires a few steps – many of them are standard for npm packages, but
getting them right is essential (more on this anon). To start, we turned our
demo folder into a package folder – this required us to run npm init -y to
give us a package.json. (The -y parameter bypasses the need for the
questions and uses sensible defaults.)

Next, we edited the package.json file to add the various fields required for
our package. These include fields such as name, type, module, and
description. Once done, we ran npm install to set up the dependencies for
our package – in this instance, Vite. To test whether it all works, we ran npm
run dev to fire up Vite’s development server before previewing the result in a
browser.

70
ChapTer 2 CreaTIng ComponenTs

At this point, we had to pause to get the files uploaded to our

repository. Once done, we ran npm login from a command prompt: this
ensures you are logged into npm with the correct account for publishing your
component. It doesn’t require any special permissions over and above what
you have by default; it’s more about ensuring your component lands in the
right area in npm!

When running npm login, it prompted for some details – we had to

enter our username and password for the repo, followed by the email
address. Notice that we applied the -access=public tag; this is necessary for
public/open repos.

Next up, we ran npm publish -access=public to publish our

component – in the same vein as login, we had to provide the -access tag.

To round things off, we then browsed to the URL given by npm for our new
component – this should be a formality, but it’s a helpful check to ensure it
displays in the npm registry.

Excellent – we have a published component! Now that we’ve

completed this step, we should also tidy up the repository and add
documentation, screenshots, and the like to make it all look presentable.

I’ve seen too many repositories where people have not done this; it doesn’t
leave a good impression!

Publishing the Component – A Postscript

Although we have reached the end of the creation and publishing process,
the journey for me wasn’t always plain sailing! There are a couple of key
points I want to bring up which will help you:

• Remember how I said taking care of the fields we added


was important? Well, on my first attempt, I omitted to

remove a space in the module="script.js" line – the

net result was that when I tried to use Skypack to create

a temporary package I could use to test the site, the

Skypack process failed!

71

ChapTer 2 CreaTIng ComponenTs

Although I could remove the space and republish,

Skypack got itself confused and still thought the

original version was present! I will need to investigate

it – hopefully, I can get a version working… I wanted to

use it to help test that our new component worked, but

it will have to wait until I can get it fixed.

• Although we have created two components that are

practical examples, it’s important to note that we

should consider them as alpha versions. We still need

to do more work before we can regard them as suitable

for use in a production environment. At this stage,

it’s more important to understand the process – once

we have this, we can use the examples to develop


something more solid for release.

• Although I’ve used demo names for our package,

I have a sneaking suspicion that other versions of

alert components exist in the npm registry that other

authors have created. It, therefore, pays to make sure

that you choose a name that doesn’t conflict. In this

instance, using the namespace is essential; otherwise,

you may find your publishing process trying to publish

to an account that isn’t yours!

These are just some things for us to consider when creating our

components. I’m sure there may be more, but that will come with
experience!

Summary

Although many developers will naturally reach for their framework of


choice when it comes to creating components, we’ve reached a point where
there may be occasions where this isn’t necessary. We can achieve 72

ChapTer 2 CreaTIng ComponenTs

the same results using plain vanilla JavaScript. We’ve covered a lot of
content around that process in this chapter, so let’s take a moment to review
what we have learned.

The central theme of this chapter was to create a component we

could use in a practical context. We created two: an alert dialog and an


exploration of a third-party version of a toggle switch. In both cases, we
built the code for the components before demoing each on a simple site.
We then switched to setting up the testing framework from the Open Web
Components group and creating some basic tests for our components.

We then rounded off the process by exploring how to package and

publish our components into the npm registry – we learned that while the
process uses the standard steps from npm, we still have to include the correct
fields to ensure our component package publishes successfully.

Phew – what a journey! We still have more to cover, though: so far, we’ve
focused on creating components that use the Shadow DOM

API. However, what if we didn’t want to develop components but still


wanted to benefit from the Shadow DOM API? Can we do that … what does
it mean for us? This opens up a few intriguing possibilities and avenues to
explore – stay with me, and I will reveal all in the next chapter.

73

CHAPTER 3

Applying Shadow

DOM API in Apps

Throughout this book, we’ve focused on creating components that use the
Shadow DOM API – it’s perfect for encapsulating styles, controlling what
we can and can’t override, and generally maintaining consistency – rather
than letting others run roughshod over your pride and joy!

There is one thing, though – what about using the API generally in sites
outside of components? As developers, we would probably create some form
of component, but there may be occasions where you want to create
something custom (i.e., that is not reusable) yet still want to benefit from
using the API.

Fortunately, we can use the API anywhere on a site – it’s not limited to just
being used in components! There isn’t anything different we need to do, but
there are a few things to consider. I’ve selected three examples to illustrate
what we might expect to see – they are as follows:

• Using the API in styling frameworks

• Hosting existing components (such as React) inside the

Shadow DOM API

• Implications of using the API for search engine

optimization (SEO)

© Alex Libby 2024

75

A. Libby, Beginning Shadow DOM API, Apress Pocket Guides,

https://doi.org/10.1007/979-8-8688-0249-2_3

Chapter 3 applying Shadow doM api in appS

I’m sure there will be more, but this is more than enough to get us started –
let’s dive in first and look at how we might use the API in a styling
framework.

Working with Styling Frameworks

Hands up – what name(s) come to mind if you think of styling frameworks?

I’ll lay odds that for some, Tailwind will feature as the answer – its
popularity is not something to be sniffed at! For those of you who have not
yet had the opportunity to use Tailwind, we must add classes to an element
using predefined tags, such as this example:

<main class="h-screen bg-purple-400 flex items-center

justify-center">
<!— remainder of markup... -->

</main>

Tailwind then converts these tags into meaningful CSS, as part of

building the site. We’re compiling the stylesheet on the fly, but only when
we run up the site for the first time. Now – this all sounds good, but there is
one drawback: we can’t use Tailwind if we want to implement the Shadow
DOM API! Is there a way around this, I wonder…? Fortunately for us, there
is – let me introduce you to one of many Tailwind “clones”: Twind.

The word “clones” is probably not the most apt word to use, as it

doesn’t copy Tailwind; it’s a small compiler that converts the Tailwind
classes into valid CSS. We then use it to construct our stylesheet at build
time. It sounds a little complicated, but it isn’t – there is a little bit we must
set up, but once in place, we treat the demo largely like we’re using standard
Tailwind. To understand what this all means, let’s dive in and look at a small
demo showing Twind in action.

76

Chapter 3 applying Shadow doM api in appS

USING A STYLE FRAMEWORK WITH THE API

to set up our twind demo, follow these steps:

1. First, go ahead and create a new folder called tailwind-in-

shadow-dom inside our project area.

2. next, open a command prompt inside this folder, which should

set the working folder for us. at the prompt, enter npm init -y,

then press enter.


3. when done, we should find a package.json file at the

root of this folder. Crack open a copy of the code download

accompanying this book, then copy the package.json from

that download over the top of this one – it contains some

dependencies we want to install very shortly.

4. revert to the prompt we had open in step 2, then enter npm

install and press enter to install the dependencies.

5. while this is happening, switch to a blank file in your editor, then add this
markup, saving it as index.html at the root of

the tailwind-in-shadow-dom folder:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8" />

<link rel="icon" href="/favicon.ico" />

<meta name="viewport" content="width=device-

width, initial-scale=1.0" />

<title>Using Web Components with Twind</title>

<script type="module" src="/index.js"></script>

</head>

<body style="margin: 0"></body>


</html>

77

Chapter 3 applying Shadow doM api in appS

6. next, open a new blank file and add this code – we’ll do it in blocks,
starting with the imports and opening statements:

import install from "@twind/with-web-components";

import config from "./twind.config";

customElements.define(

"twind-element",

class TwindElement extends install(config)

(HTMLElement) {

constructor() {

super();

const shadow = this.attachShadow({ mode:

"open" });

7. leave a line blank, then add this statement – it acts as the

template for our component, with the tailwind styling included:

shadow.innerHTML = `

<main class="h-screen bg-gray-400 flex

items-center justify-center">

<h1 class="font-bold text(center 4xl white


sm:gray-800 md:pink-700)">

Running Tailwind inside Shadow DOM

</h1>

</main>

`;

);

document.body.innerHTML = "<twind-element></twind-

element>";

78

Chapter 3 applying Shadow doM api in appS

8. Save the file as index.js at the root of the folder. For tailwind to work
correctly, though, we need to add a small configuration

file – this we take care of in tailwind.config.js, so

open a new file and add this code, saving it as tailwind.

config.js:

import { defineConfig } from "@twind/core";

import presetAutoprefix from "@twind/preset-

autoprefix";

import presetTailwind from "@twind/preset-tailwind";


export default defineConfig({

presets: [presetAutoprefix(), presetTailwind()],

/* config */

});

9. Save and close all open files, then switch to a

command prompt.

10. Make sure the working folder is set to the tailwind-in-

shadow-dom folder; then at the prompt, enter npm run

start and press enter.

11. go ahead and browse to http://localhost:5173 – if all is well, we should


see this text appear in your browser (Figure 3-1).

79
Chapter 3 applying Shadow doM api in appS

Figure 3-1. Tailwind running inside an instance of the

Shadow DOM API

Perfect – we can now use Tailwind, the package we all know and love!

Okay – some of you may not be so enamored with the classes we have to
apply, but hey, it’s all within the markup, so we don’t have to switch files,
and Tailwind deals with a lot of the heavy lifting for us. Although most of
what we’ve done is standard Tailwind code, there are a couple of points of
note in that demo, so let’s pause for a moment to review the changes in more
detail.

Exploring the Code Changes

Cast your mind back about 20 years. Do you remember a time when

people had to code styles by hand?

Yes, I’m old-school enough to remember that time; gone are the days of
crafting each rule by hand, knowing which browsers support which
attributes, and the quirks associated with rendering content in browsers.

We now have frameworks or libraries such as Tailwind, Sass, and the like to
do much of the heavy lifting for us! With that in mind, it’s essential to ensure
they still work if we want to use tools such as the Shadow DOM API.

80

Chapter 3 applying Shadow doM api in appS

Fortunately, some of this compatibility work has already been done – in this
last demo, we used Twind, a small compiler that can turn standard Tailwind
classes into styles that work in the Shadow DOM. To prove this, we first set
up a mini-site using npm, ready to import the dependencies we need for our
site. We then created the main index.html file, which hosts our Tailwind-
driven component in standard markup.
Next up, we built the core of our site: the component. We first imported two
items – one is the twind package for web components, and the other is the
twind configuration for our site. We then added the opening block for our
component using customElements.define() – this we set as twind-element,
with a slight change to the config, so it defines a TwindElement object using
the twind configuration. In the second part of the code block, we then
attachShadow(), which we assign to shadow, before inserting the
innerHTML containing the markup for our component. It consists of
standard Tailwind classes and HTML markup – the background will be
purple with either gray or pink text, depending on our screen size. The most
important part is at the end – we define the <twind-element>

</twind-element>, which will be our component and which we add to the


body of the page.

Moving on, we created tailwind.config.js, which sets up the

configuration for Tailwind. Here, we set it to use the core properties and
standard Tailwind and Autoprefixer presets. We then round out the demo by
running up the site in the Vite development server and previewing the results
in our browser. We can prove the Tailwind configuration is working by
taking a peek at the code in the console log first (Figure 3-2).

81
Chapter 3 applying Shadow doM api in appS

Figure 3-2. An extract of the original Tailwind code in our component

Let’s now also look at an example from the compiled styles in our

browser (Figure 3-3).

Figure 3-3. An extract of the compiled Tailwind styles

At first glance, this doesn’t look too dissimilar to standard CSS – but notice
the constructed stylesheet entry on the right.

This is a relatively newish concept, where we construct the stylesheet at


compilation time; this is how we can build stylesheets when using tools such
as Tailwind. It does mean that we can change properties if needed; we can
also share the constructed stylesheet between a

document and a Shadow DOM tree using methods such as ShadowRoot.

adoptedStyleSheets and Document.adoptedStyleSheets.

82

Chapter 3 applying Shadow doM api in appS

if you would like to learn more, then head over to the Mdn
documentation site for this feature, which is at https://

developer.mozilla.org/en-US/docs/Web/API/

CSSStyleSheet/CSSStyleSheet.

Okay, let’s crack on: so far, we’ve focused on individual web

components. Up until now, we’ve focused primarily on components.

While this works very well, the downside is that we work with individual
units. Each component would attach itself to the Shadow DOM. If this is
enabled, we’d have a mix of styles in different places and must use different
methods to access some style properties if they are encapsulated in the
Shadow DOM. What an absolute pain…! Can we do anything about this, I
wonder?

Mounting a React App Using

Shadow DOM API

The answer to the question at the end of the last section is yes: What if we
could host the entire application in an instance of the Shadow DOM?

Yes, you heard me correctly – I did say the whole application! It might
sound a little bonkers at first, but there is an excellent reason for doing this;
before I divulge details, let’s first take a look at how we might host that
example application in the Shadow DOM.

MOUNTING A REACT APP IN THE API

For this demo, i will create a straightforward demo using the Checkbox
component from radix, available from www.radix-ui.com, a Ui library
written for react. to see how i set it up, follow these steps:

83

Chapter 3 applying Shadow doM api in appS


1. First, go ahead and create a folder called react-in-shadow-

dom inside our project area.

2. we will use Vite to create our react app – we could install each
dependency manually, but to save time, i’ve created the contents

of a package.json we can use to install everything in one go.

in a new file, add the following code, saving it as package.

json at the root of the react-in-shadow-dom folder:

"name": "react-in-shadow-dom",

"version": "1.0.0",

"description": "",

"main": "src/index.jsx",

"keywords": [],

"author": "",

"license": "MIT",

"scripts": {

"start": "vite",

"build": "vite build",

"preview": "vite preview"

},

"dependencies": {
"@radix-ui/colors": "^3.0.0",

"@radix-ui/react-checkbox": "^1.0.4",

"@radix-ui/react-icons": "^1.3.0",

"react": "^18.2.0",

"react-dom": "^18.2.0"

},

"devDependencies": {

"vite": "^4.0.1"

84

Chapter 3 applying Shadow doM api in appS

3. next, open a new file, then add the following markup – save

this as index.html at the same root:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8" />

<link rel="icon" href="/favicon.ico" />

<meta name="viewport" content="width=device-


width, initial-scale=1.0" />

<title>Hosting a React App in Shadow DOM</title>

<script type="module" src="/index.jsx"></script>

</head>

<body>

<div id="container">

<span>Please click the box to accept the terms

and conditions:</span>

<div id="react-root"></div>

</div>

</body>

</html>

4. this is where the magic happens – we need to build the app

inside our project. we can do this with the following code, so

crack open a new file and add this to it:

import React, { FC } from "react";

import { render } from "react-dom";

import * as Checkbox from "@radix-ui/react-

checkbox";

import { CheckIcon } from "@radix-ui/react-icons";


import "./styles.css";

85

Chapter 3 applying Shadow doM api in appS

const App = () => (

<form>

<div style={{ display: "flex", alignItems:

"center" }}>

<Checkbox.Root className="CheckboxRoot"

defaultChecked id="c1">

<Checkbox.Indicator className="CheckboxIndicator">

<CheckIcon />

</Checkbox.Indicator>

</Checkbox.Root>

<label className="Label" htmlFor="c1">

Accept terms and conditions.

</label>

</div>

</form>

);

const root = document.querySelector("#react-root");


root.attachShadow({ mode: "open" });

render(<App />, root.shadowRoot);

5. Save the file as index.jsx at the root of the react-in-shadow-

dom folder.

6. next, we will add some basic styling – this isn’t essential to the project,
but given we’re using a radix component, it’s useful to

show that it will still work as expected. open a new file, then

add the following styles, saving it as styles.css:

@import "@radix-ui/colors/black-alpha.css";

@import "@radix-ui/colors/violet.css";

/* reset */

button { all: unset; }

86

Chapter 3 applying Shadow doM api in appS

#container { width: 450px; display: flex; flex-

direction: column; margin: 30px auto 0 auto; border:

1px solid #000000; font-family: Arial, Helvetica,


sans-serif; }

span { background-color: #ffffff; padding: 5px; }

#react-root { padding: 10px; background-color:

#d3d3d3; display: flex; height: 50px;}

.CheckboxRoot { background-color: #ffffff; width:

25px; height: 25px; border-radius: 4px; display:

flex; align-items: center; justify-content: center;

box-shadow: 0 2px 10px var(--black-a7); }

.CheckboxRoot:hover { background-color:

var(--violet-3); }

.CheckboxRoot:focus { box-shadow: 0 0 0 2px #000000;}

.CheckboxIndicator { color: var(--violet-11); }

7. Save and close all open files. Switch to a command prompt,

then make sure it’s set to the react-in-shadow-dom folder

as the working folder.

8. enter npm run start at the prompt and press enter to start

the Vite development server. when prompted, browse to http://

localhost:5174/. if all is well, we should see something similar

to that shown in Figure 3-4.

Figure 3-4. Demo of a React app inside Shadow DOM


87

Chapter 3 applying Shadow doM api in appS

9. at face value, it doesn’t really look any different from a regular react app –
could you tell if it was hosted in an instance of

the Shadow doM? probably not – we will only know once we

crack open the browser console and peer under the hood. the

evidence that we’re indeed running in the Shadow doM is

shown in Figure 3-5.

Figure 3-5. Proof we're running the React app in the Shadow DOM

Fantastic – even though we created a straightforward application using one


component, it still shows we can take advantage of the Shadow DOM

API. Granted, we used React, but there is no reason why this shouldn’t work
for other frameworks: most front-end ones are written using
JavaScript, so it should still work.

That all aside, there are a couple of interesting points in this last demo that
help make it all work – let’s take a moment to dive in and explore the code
in more detail.

88

Chapter 3 applying Shadow doM api in appS

Understanding What Happened

To set up our demo, we kicked off by creating a standard Vite application


using a prebuilt package.json – inside this, we set up dependencies for the
Radix UI colors, react-checkbox, and icons, as well as the standard packages
for React. We then put together a simple index.html file, which we use to
display our component; this calls index.jsx from the /src folder, which
represents our component.

Inside the component file, we first import various packages – React and FC
(or Functional Component) packages, as well as our checkbox component
and the Radix icons.

We then create an App() instance – inside this, we add the markup for our
Checkbox instance; there’s nothing complex here, but the real magic
happens at the end of the file. Here, we query the DOM for the #react-root
selector; once found, we attach an instance of the Shadow DOM to it before
using the render method to display it all on screen. To finish the demo, we
also added a simple stylesheet – this included both styles for the container
markup and those used in the checkbox component itself.

i should mention at this point that if we had built our checkbox

component with the Shadow doM api, then we wouldn’t have

been able to change styling within it unless it had been set up

with ::host or ::part tags. as we’re encapsulating the whole


application in the api, this constraint is no longer relevant – it’s what others
do to our app that counts!

At this point, you’re probably thinking – how is this technique

useful? It’s a great question: being able to do something doesn’t mean we


should do it!

89

Chapter 3 applying Shadow doM api in appS

There is, however, a good reason for using this technique: in a themed
environment. Imagine you create an online application that others use (such
as an online form) in a themed environment. If they change the theme, you
want to be sure that the theme’s styling won’t affect your own –

one way to do it is to host it inside an instance of the Shadow DOM API.

Implications of SEO and the API

Search engine optimization – or as we developers know it, SEO.

Three letters that are probably the most critical to the success of any website
– we want our offering to be visible, available to all, and searchable by the
likes of Google and Bing. After all, that’s how the revenue comes in, right?

That all aside, implementing the Shadow DOM API can have an effect on
SEO – in some instances, content may render fine (using the Light DOM

approach) or may not even render at all (using the attribute approach).

This quirk may seem odd to you, as content should at least render, even if it
isn’t styled? Well – this is one of the challenges we face with using the
Shadow DOM API: to understand better what this means for us, let’s dive
into a demo that shows what happens in more detail.

Before we get started, though, there are a couple of things you should be
aware of:
• You might want to do this demo over two days – a part

of the process requires Google to index your test site

before we can assess the SEO implications; this can

take a while to complete!

• You will need your GitHub account credentials for

this – they are required in order to authenticate access.

• We will be setting up an account with Netlify – although

there is usually a cost for this, using the free tier is fine

for this demo.

90

Chapter 3 applying Shadow doM api in appS

Okay – with the formalities out of the way, let’s move on to starting the
demo.

TESTING SEO PART 1: SET UP THE SITE

to test the effects of different implementations, follow these steps: 1. First,


go ahead and download a copy of the dom-and-seo

folder from the code download – this contains a simple demo

highlighting the various ways we can use the Shadow doM api.

2. next, browse to www.netlify.com and log in – use your

github credentials to authorize access and set up the account.

3. when completed, select the option to drag and drop the code
folder onto the site and wait for it to confirm it has published

your code.

4. when prompted, grab the Url of your published site – go

ahead and browse to confirm you can see content appear.

Keep a copy of the Url you got from step 4 – you will need it for the next
part of this demo.

With the test site set up, we can move on to the next stage: linking your site
to Google Search Console Tools.

TESTING SEO PART 2: CHECKING WITH GOOGLE


WEBMASTER TOOLS

to find out what google thinks of our site, follow these steps:

1. go to https://search.google.com/search-console,

then click Start now.

2. when prompted, use your github credentials to authenticate

access, then run through the verification process.

91
Chapter 3 applying Shadow doM api in appS

3. once logged in, enter your site’s Url in the search box at

the top.

4. on the right side of the page, select request indexing.

5. google will perform some checks. when it is complete, you

should see “Url is available to google.”

6. next, click View tested page, then the Screenshot tab.

7. we can see everything is rendered successfully (Figure 3-6).

Figure 3-6. Our tested page

8. Click Url inspection ➤ View tested page, then the htMl tab.

we can see the htMl markup behind the demo – even though
we have four different instances rendering on the page, all

display text on the screen correctly, which can be read for Seo

purposes.

92

Chapter 3 applying Shadow doM api in appS

So far, so good – we can see how Google would see our site and

confirm that we get four instances rendered, along with the relevant markup
for each instance. But what if we tried the same test in Bing? Will it show
the same content, or will it display something different, I wonder…?

Comparing with Bing

Although Google has become the ubiquitous browser we all know and

love, we must not forget that other browsers exist – Microsoft’s Bing being
one of them!

What works in Google may or may not work as well (or at all) in Bing, so
we should at least check both browsers when using the API. Fortunately,
both use a similar process, although Bing is good enough to import settings
from Google’s checker, which will bypass some of the procedure.

Let’s take a closer look at what we need to do.

a heads-up: i used a gmail account for this demo, which makes life easier;
you may want to do the same or adapt the steps accordingly if you prefer to
use a different method.

COMPARING WITH BING

to check our site against Bing’s tool, follow these steps:


1. First, browse to https://bing.com/webmaster, then sign in and choose the
option to use google.

2. next, follow the prompts to authenticate with your

chosen method.

3. once done, click import from google Search Console, then hit

Continue.

93

Chapter 3 applying Shadow doM api in appS

4. on the next step, select Choose account – used google, then

select allow ➤ import, then Continue.

5. next, click Url inspection, then enter the Url of your test

netlify site into the Url inspection field, and hit inspect.

6. once done, click request indexing ➤ Submit, then wait a few

minutes before clicking the live Url tab.

at this point, Bing should show it’s found 2 Seo issues – they
will be missing meta description and language tags.

7. next, click Seo ➤ Site Scan, and enter attempt 1 for the

name field.

8. next, add the Url of your test site and set the page limit to 10.

(we could even go down to one page, but ten will give us a bit

of leeway). Click oK to continue.

at this point, you will need to wait – the page will update with issues found,
but this can take a while to do as it depends on Bing indexing our site. it
might be easier to return to this demo later or the following day.

Thankfully for us, I’ve gone ahead to see how Bing sees our site – the results
are not as good as Google! To see what I mean, this is what our site page
shows when clicking URL Inspection ➤ Live URL ➤ View Tested Page
once the site has been indexed by Bing (Figure 3-7).

Figure 3-7. This is what Bing sees when indexing our site 94

Chapter 3 applying Shadow doM api in appS

Notice how, when rendered in Google Chrome, we could see all

four instances rendered correctly? In Bing, we only get three instances


rendered – to see what I mean, try rendering the site in IE11 mode. In our
case, Bing doesn’t render each entry with the same font as Chrome; it also
drops the Header without a Slot instance.
if you’re not sure how to switch to ie11 mode in edge, then search for

“render edge in ie11 mode” on google – there are plenty of articles that will
explain the process.

To see what I mean, Figure 3-8 displays what we see when looking under the
hood in Edge’s browser console log area.

Figure 3-8. Evidence that the last entry is not rendered correctly in IE11

It does show that even though using the Shadow DOM API can be

beneficial for encapsulating styles, we can’t use all the options we tried in
the demo, as not all browsers will render them as expected!

Challenges with Server-Side Rendering –

A Postscript

Throughout this book, we’ve talked about different ways of rendering


content using the Shadow DOM API – some might force us to render static
content, or we can create templates for reusability. While this can work 95

Chapter 3 applying Shadow doM api in appS

very well, it can present a challenge for SEO, which is why we may need to
consider which method of encapsulation we use when working with the API.

If we’re building a site using a framework such as React, we would likely


use server-side rendering to help cache content and speed up site delivery.

There is an issue here, though: while the skeleton of the page may be quick
to load, we have to get data for the page separately, so this could take longer,
at least for the first page. It’s not helped by the fact that we frequently might
change content using JavaScript, which can cause a rendering mismatch
between client and server! It can affect SEO, as we need to see all the
content; if content is delivered by JavaScript and not static HTML, it won’t
see what we’re trying to render on-screen.
To get around this, we should consider using Declarative Shadow DOM

(or DSD) – we touched on this earlier in this book. It allows us to write the
HTML template directly in the component and add it to a Custom Element
using a template element with a shadowrootmode attribute.

Using this method has two benefits – it’s faster and means we can

deliver content without JavaScript, reducing the impact on SEO for the site.

There is also a bonus: DSD only attaches a Shadow Root to an element. In


other words, DSD takes care of rendering the HTML in our component; we
still need to register and make it interactive as a Custom Element.

This process is known as hydration – we need to supply a class definition to


load it, plus any event handlers; the bonus is that we can choose when we
want to trigger this process. If someone browsing through the site isn’t
interacting with it (i.e., clicking buttons, etc.), rendering the content is
enough; we only need to hydrate if the end user is performing an action on
the site.

This is one of many things we must consider when using the Shadow

DOM API – it’s still a relatively young technology. While developers are
likely to favor frameworks such as React, I suspect there will come a time
when we shift away from using them to more native code we can run

directly in our browser without runtime libraries.

96

Chapter 3 applying Shadow doM api in appS

Summary

One of the great things about using the Shadow DOM API (and, by

extension, Web Components) is not only their interoperability between


component frameworks but that we can also use them in a broader
context, such as styling or even adding support to applications! In this
chapter, we explored three examples of how we might do this; let’s take a
moment to review what we have learned.

We started by exploring how styling frameworks can use the Shadow

DOM API – for this, we used Tailwind as our example in the guise of Twind.

This was interesting as we typically can’t render Tailwind styles in the


Shadow DOM API without some help, so it was great to see a lightweight
compiler available to help in this respect!

For the second example, we took the principles of Shadow DOM

API that we’ve used so far and extended them to mount an entire React
application inside an instance of the Shadow DOM. We saw how, instead of
worrying about using different methods of styling elements, we could do all
of it using the same process – this is great for those who have apps hosted in
a themable environment and want to make sure themes do not affect
functionality.

For the last example, we dug a little deeper and looked at three letters that
are super important to any site: SEO. We saw how our choice of writing
Shadow DOM–enabled components can have an effect and learned that

DSD is probably the best one to ensure reusability while not affecting SEO!

Phew – it seems a lot, but we’re making good progress on our

introductory journey through the world of the Shadow DOM API! We still
have a few things I want to explore with you, such as performance, adding
support to existing components, and more – as well as ask ourselves this
question: “Will Web Components ever replace the likes of React?”

Intrigued? Stay with me, and I’ll explain more in the next chapter.

97

CHAPTER 4
Supporting Other

Frameworks

Now that we have an understanding of the Shadow DOM API and how it
operates, it is time to turn our attention to the broader picture – what if we
use a framework such as React or Svelte?

It presents a challenge, given that not all frameworks use Shadow DOM

API by default. All is not lost, though – take, for example, React: with some
work, we can use the API without too much difficulty. In this chapter, we’ll
explore some of the tricks we can use to reconfigure our sites to allow us to
run the Shadow DOM API and see that we can use it both in components
and elsewhere on our projects.

We have a lot to cover, so without further ado, let’s start with a look at
adding Shadow DOM API support to React.

Adding Shadow DOM API Support

to React Components

Hold on a moment – you said React. Surely, that’s what we’ve been doing
until now, right?

Yes, that is what we’ve been doing, but this time, there is a twist: we will
add Shadow DOM API support to existing components. Let me explain
more.

© Alex Libby 2024

99

A. Libby, Beginning Shadow DOM API, Apress Pocket Guides,

https://doi.org/10.1007/979-8-8688-0249-2_4

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS


So far, we’ve learned about using the Shadow DOM API in new

components, which (albeit simple ones) we’ve built from the ground up.

However, there will be components out there that we use and where we
would like to add support. What can we do?

In some cases, we might be able to edit the source code and add it directly –
however, that will probably be few and far between. Instead, we can create a
component to wrap an existing one in the markup to have the same effect
when compiling it during the build process. There is a bonus to this: we can
reuse the code across multiple element types, limited only by those element
types the API supports.

Now – I know others have already created examples, but many of these are
outdated and may not work, at least with current versions of React.

An example is the component available from https://apearce.github.

io/react-shadow-root/, but this one hasn’t been updated in four to five years!

Instead, I will take a different route – we’ll create a simple React application
using Vite, but this time, create our own ShadowRoot

component. As an example, I’ll wrap it around some MUI buttons, but the
same principle should apply to different components.

DEMO – ADDING SHADOW DOM SUPPORT

Creating the core part of our component isn’t difficult – we might want to
test and develop it further before using it in a production environment!

To add Shadow DOM support to an existing component, follow these steps:


1. First, go ahead and fire up a terminal session from the project folder;
make sure it shows the working folder as being the

project folder.

2. At the prompt, enter npm create vite@latest, and


press Enter.

100

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

3. Vite will prompt for some options – when prompted, choose the
following:

√ Project name: ... adding-shadow-dom-support

√ Select a framework: » React

√ Select a variant: » JavaScript

4. next, go ahead and extract a copy of the package.json for

this demo from the code download accompanying this book.

5. At the prompt, enter cd adding-shadow-dom-support,

then enter npm install && npm run dev and press Enter.

6. while node.js creates our site, we can now create the various

files – we’ll start with updating App.jsx at the root with this code: import
React from "react";

import { ShadowRoot } from "./components/ShadowRoot";

import BasicButtons from "./components/Button";

import "./App.css";

function App() {

return (

<>
<BasicButtons />

<ShadowRoot>

<BasicButtons />

</ShadowRoot>

</>

);

7. Save and close the file. next, in a new file, add this code, saving it as
Button.jsx in the \src folder:

import * as React from "react";

import Stack from "@mui/material/Stack";

import Button from "@mui/material/Button";

101

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

export default function BasicButtons() {

return (

<Stack spacing={2} direction="row">

<Button variant="text">Text</Button>

<Button variant="contained">Contained</Button>

</Stack>

);
}

8. we have one more component to build – it’s ShadowRoot.js.

go ahead and add the following code to a new file, saving it as

ShadowRoot.js in the \src folder:

import React from "react";

export class ShadowRoot extends React.Component {

attachShadow(host) {

if (host == null) {

return;

host.attachShadow({ mode: "open" });

host.shadowRoot.innerHTML = host.innerHTML;

host.innerHTML = "";

render() {

return <span ref={this.attachShadow}>{this.props.

children}</span>;

9. Last but by no means least, we need to update index.html in


the \public folder – go ahead and replace the contents of that

file within this code:

102

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8" />

<link rel="icon" type="image/svg+xml" href="/

vite.svg" />

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>Adding Shadow DOM Support Demo</title>

</head>

<body>

<div id="root"></div>

<script type="module" src="/src/main.jsx"></script>

</body>
</html>

10. Save and close all open files. next, revert to a terminal session, then enter
npm run start at the prompt to fire up the Vite

development server.

11. Once running, go ahead and browse to the urL when prompted.

if all is well, we should see our component now operating with

Shadow DOM support, as shown in Figure 4-1.

Figure 4-1. The component – now running with Shadow DOM

API support

Okay – nothing looks like it’s changed … to be doubly sure, let’s peek at the
underlying code in our browser’s console (Figure 4-2).

103

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

Figure 4-2. Proof that we are using the Shadow DOM


Wow – a lot is going on there! Most of the code we created in this demo is
primarily for the application itself; the magic happens in ShadowRoot.js.

It’s a valuable little component that we can drop in any location where the
Shadow DOM API is supported, and it will add it automatically. We covered
some helpful pointers in this code, so let’s take a few moments to review the
changes in more detail.

Breaking Apart the Code Changes

Until now, we’ve worked on adding Shadow DOM support for new

components – existing components can use the same principles, but as we’ve
seen, we have to nest them inside our ShadowRoot component. We end up
with some extra tags in the markup, but we might have to live with this in
the short term.

That all aside, to create the component, we started by creating a simple test
site using the standard process for all Vite sites, ready to host our new
component. Once done, we added the various files, starting with App.jsx as
the main page for our site. I’ll return to this page after reviewing the other
files we created in this demo.

104

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

We then created a new component called BasicButtons – in this

component, we import an instance of the Stack and Button components from


MUI, which we then render on the page. In reality, I could have chosen to
use any components here or a completely different library; the critical thing
to note is that we use an element supported by the Shadow DOM API (in this
case, button).

Next up comes the most critical part – our ShadowRoot component.

We set it up as a React.Component class, inside of which we create an


attachShadow function, which defines an instance of the Shadow DOM to
apply to our target element. We first check to ensure there is a valid host
element and exit if not; assuming we have one, we then attachShadow the
Shadow DOM and assign the contents of innerHTML to our host. In the
render() method, we return an instance of the element, with the Shadow
DOM attached, before rendering it on screen.

At this point, let’s return to the App.jsx page – we updated it to include two
instances of our BasicButtons component. The first is the standard version
from MUI; the second we wrapped inside our ShadowRoot component.
Notice something about the latter and that it doesn’t appear to have any
styling?

It’s an odd side effect but not entirely unexpected: I suspect that the MUI
component doesn’t have a ::host attribute, which would allow us to style the
components from externally. It’s something to remember if you plan on
using this method; you may find that you must alter styles within the
component to allow it to render as expected!

Okay – let’s crack on: so far, we’ve seen a few examples of using React with
the Shadow DOM API. I think it’s time for a change – I know React is super
popular, but what about other frameworks or libraries out there?

How do they support the API…?

105

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

Exploring Other Framework Examples

I hinted earlier that not every framework supports the Shadow DOM API, or
may cater for it differently. To see what I mean, let’s go through a couple of
simple examples of other frameworks – Svelte and Alpine.js.

The former abstracts much of the boilerplate away, allowing you to focus on
the important stuff; the latter is a framework but much smaller and still
supports the API. Let’s dive in and take a closer look, beginning first with
the Svelte example.
First Example: Using Svelte

As frameworks go, Svelte is a relative newcomer to the scene; however, it is


quickly getting traction. It was designed to abstract away some of the more
mundane tasks, allowing you to focus on what’s important: writing code!
The best way to think of it is writing a CodePen but on steroids … but I
digress.

I chose Svelte as it makes it very easy to add Shadow DOM support –

we have to add a few lines of code into the configuration and then add a
simple tag to the top of a component, and Svelte takes care of the rest.

For this next demo, I’ve adapted something I found online – it uses a slightly
different coding style, but that shouldn’t matter: it’s the principle of using
customElements that counts! I’ll explain more later in this chapter when we
review the changes made, but for now, let’s get down and dirty with coding
the next demo.

106

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

FRAMEWORK EXAMPLE PART 1: SVELTE

To build our Svelte example, follow these steps:

1. First, we need to create a starting site to host our component –

for this, fire up a git Bash session, then make sure the working

folder is set to our project area.

2. At the prompt, enter npm create svelte@latest

comparing-to-svelte and press Enter.

3. Svelte will ask a few questions about how we want to create


our site – when prompted, choose the following answers:

• which Svelte app template? Skeleton project

• Add type checking with TypeScript? No

• Select ESLint for code linting, Add Prettier for code formatting, Add
Playwright for browser testing, and Add Vitest for unit testing.

4. when done, press Enter, and wait for Svelte to display Your

project is ready!

5. with the site in place, we have a few files to create or update –

the first is our component, rate. Crack open a new file, then

add the following code, block by block, starting with some

imports and variable definitions:

<svelte:options customElement="my-rating" />

<script>

import { onMount, beforeUpdate } from "svelte";

export let value = 0;

export let length = 5;

export let showCount = false;

107

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

export let ratedesc = [];

export let beforeRate;


export let afterRate;

export let disabled = false;

export let readonly = false;

let arr = [];

let rate = 0;

let over = 0;

$: if (value) {

rate = convertValue(value);

over = convertValue(value);

6. Leave a line blank, then add these functions:

const convertValue = val => {

if (val >= length) {

val = length;

} else if (val < 0) {

val = 0;

return val;

};

const onOver = index => {


if (!readonly) over = index;

};

const onOut = () => {

if (!readonly) over = rate;

};

const setRate = index => {

if (readonly) return false;

108

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

if (typeof beforeRate === "function") {

beforeRate(rate);

rate = index;

if (typeof afterRate === "function") {

afterRate(rate);

};

7. This next block takes care of a second bunch of functions

needed for our demo:

const isFilled = index => {


return index <= over;

};

const createArray = () => {

for (let i = 1; i <= length; i++) {

arr.push(i);

};

beforeUpdate(() => {

if (arr.length === 0) {

createArray();

});

onMount(() => {

value = convertValue(value);

rate = convertValue(value);

over = convertValue(value);

});

</script>

109

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS


8. Miss a line, then add this style block:

<style>

div {

font-family: Arial, Helvetica, sans-serif;

.icon {

display: inline-block;

width: 16px;

height: 16px;

stroke-width: 0;

stroke: currentColor;

fill: currentColor;

vertical-align: middle;

top: -2px;

position: relative;

margin: 0 5px;

.Rate {

cursor: default;

}
.Rate__star {

color: #dedbdb;

display: inline-block;

padding: 7px;

text-decoration: none;

cursor: pointer;

background: transparent none;

border: 0;

110

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

.Rate__star .icon {

top: 0;

vertical-align: middle;

.Rate__star.hover {

color: #efc20f;

.Rate__star.filled {

color: #efc20f;
}

.Rate__star:hover,

.Rate__star:focus {

text-decoration: none;

.Rate__view .count,

.Rate__view .desc {

display: inline-block;

vertical-align: middle;

padding: 7px;

.Rate__star[disabled] {

opacity: 0.8;

.Rate__star.hover[disabled],

.Rate__star.filled[disabled] {

color: #efc20f;

opacity: 0.6;

</style>
111

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

9. we finally come to the meat of the component – miss a

line, then add this Svelte script. we’ll start with the initial

opening markup:

{#if length > 0}

<div class="Rate">

<svg

style="position: absolute; width: 0; height: 0;"

width="0"

height="0"

version="1.1"

xmlns="http://www.w3.org/2000/svg"

xmlns:xlink="http://www.w3.org/1999/xlink">

<defs>

<symbol id="icon-star-full" viewBox="0 0 32 32">

<title>star-full</title>

<path

d="M32 12.408l-11.056-1.607-4.944

-10.018-4.944 10.018-11.056 1.607 8


7.798-1.889 11.011 9.889-5.199 9.889

5.199-1.889-11.011 8-7.798z" />

</symbol>

</defs>

</svg>

10. This next part takes care of rendering each star as a button, creating the
star SVg, and setting the appropriate event

handlers:

{#each arr as n}

<!-- svelte-ignore a11y-mouse-events-have-key-

events -->

<button

type="button"

112

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

key={n}

class:hover={n <= over}

class:filled={n <= rate || isFilled(n)}

class={'Rate__star'}

on:mouseover={() => { onOver(n); }}

on:mouseout={() => { onOut(n); }}


on:click={() => { setRate(n); }}

on:keyup={() => { onOver(n); }}

on:keyup.enter={() => { setRate(n); }}

{disabled}>

<svg class="icon">

<use xlink:href="#icon-star-full" />

</svg>

</button>

{/each}

11. we’re almost there for the component – the last part is to add the markup
that will render both the selected stars and text in

our component:

<div class="Rate__view" class::disabled={disabled}>

{#if showCount && over > 0}

<span class="count">{over}</span>

{/if}

{#if ratedesc.length > 0 && over > 0}

<span class="desc">{ratedesc[over - 1]}</span>

{/if}

</div>

</div>
{/if}

113

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

At this point, have a break for a moment – we’ve done the hard

part of this component! we still have two more files to edit,

which are +page.svelte and svelte.config.js. when

you’re ready, let’s crack on with the +page.svelte.

12. go ahead and open the +page.svelte template file that’s in

the \src\routes folder – inside it, remove everything there,

and replace it with this code:

<script>

import Rate from "../lib/components/Rate.svelte";

</script>

<style>

p { font-family: Arial, Helvetica, sans-serif; }

</style>

<p>A Rate Web Component Demo</p>

<my-rating

length={5}

ratedesc={['Very bad', 'Bad', 'Normal', 'Good',


'Very good']}

showCount={true}>

</my-rating>

13. The last file we need to change is svelte.config.js, so that

Svelte knows that we’re creating web components – for this,

edit the file so it looks like this:

import { vitePreprocess } from '@sveltejs/vite-

plugin-svelte';

export default {

preprocess: vitePreprocess(),

compilerOptions: {

114
ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

customElement: true

};

14. go ahead and save all the open files, then close them.

15. next, switch to a command prompt – enter npm run dev and

press Enter; when prompted, browse to the urL shown. if

all is well, we should see something similar to that shown in

Figure 4-3, where i’ve already selected the normal setting or three stars.

Figure 4-3. Our rating web component in action

As we’ve seen before, it looks like a perfectly normal component – the real
magic, though, is in what we see if we browse in the console log area.

Figure 4-4 shows what it looks like, and we can see the #shadowroot in use.

Figure 4-4. Under the hood of the rating component

115
ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

Phew – that was a mammoth exercise, but in my defense, most of it

was for setting up the component! Despite the somewhat lengthy code, the
changes we need to make to turn a Svelte component into a web one are
minimal – you may have noticed that we don’t even add attachShadow(), or
any of the Shadow DOM API calls, as we have done in previous demos!

This simplicity is what I like about Svelte – it’s designed to allow us to focus
on the critical tasks at hand and not worry about features it can automate or
abstract away from us. With that in mind, let’s take a walk through the code
we created in more detail.

Exploring the Code in Detail

Throughout this book, we’ve focused on adding Shadow DOM support –

we’ve had to use attachShadow() a few times, specify the open property, and
so on. Svelte is different: we haven’t had to do any of that! What’s going on,
I hear you ask…?

It comes down to one of the key tenets of Svelte – don’t bother

developers with mundane tasks that it can automate! Instead, it lets us deal
with the critical stuff, namely, the HTML, JavaScript, and CSS needed for
our feature.

We began way back when by first setting up a holding site using

the create-svelte template in Vite – Svelte is npm-driven, so it needs


something to run. Once done, we put together our component – we first
specified the all-important customElement tag before adding a bunch of
exports, a single import for onMount, and beforeUpdate from Svelte.

Next, we set a handful of variables and a Svelte reactive (or $:)

statement. The latter is a little tricky to understand, but in a nutshell, it


allows us to run any statements as soon as a variable changes value. In this
case, we set value – if this changes, then we run convertValue(value) and
assign the result to both rate and over. The following two functions manage
the latter two – we use them to make sure we have a suitable value we can
use to render the correct number of stars on the page.

116

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

We then created a second batch of functions – isFilled, which

works out if the star should be filled (i.e., selected); createArray, which
builds the number of stars to display; beforeUpdate, which triggers the
createArray function; and onMount, which runs the convertValue()

function when the component is initialized on-screen.

Moving on, we added a whole host of styles. There isn’t anything

unusual here except that we use the BEM notation of styling. The method
simplifies instances where we might otherwise have to specify multiple
layers of elements to target the right one.

You can learn more about BEM in a great article on the CSS-

Tricks website at https://css-tricks.com/bem-101/, or a search on google will


turn up alternatives!

For the last part of the component, we set up the markup for each star –

we use Svelte’s if conditional to display the appropriate number based on


what we pass to the component. Inside the conditional check, we add
markup for a star SVG image, followed by a button for each star. We style
the buttons to look like the stars we would expect to see when clicking a
review component like ours. To finish it off, we add two if statements to
render the value of the star selected and an equivalent rating in plain text on
the screen.
Phew – with that monster review out of the way, the rest will seem like a
walk in the park! To display our component, we edited the +page.svelte file
to add an instance – in this case, we could pass in some props such as length
(5), rating descriptions, and a Boolean to control the display of the text. The
real magic happens in the final change. In svelte.config.js, we add the
compilerOptions attribute, which tells Svelte to compile our component as a
web component, and automatically add support for the Shadow DOM API.

117

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

Okay – let’s move on: for our next demo, I’ve elected to use a

framework that isn’t anywhere near as popular as React, but that shouldn’t
matter: it still supports the Shadow DOM API, which is more important!

The tool in question is Alpine.js: it was designed to be a lightweight and


rugged alternative to some of the big heavyweights we use today.

I know some of you may or may not have heard of it or even ask why I’ve
chosen it! The answer is simple: why not? I like to be different – it shouldn’t
matter what the tool is, as long as it supports the API, that’s more important.
With that in mind, let’s dive in and look at how we can set up a component
with Shadow DOM API support.

Second Example: Exploring Alpine.js

Alpine.js is not something I’ve admittedly used before, but as a developer, I


always like to experiment with different tools, particularly for the front end.

I came across Alpine some time ago and love its simplicity; it’s designed to
be a lightweight, rugged alternative and works more like jQuery but for the
modern web.

Given that we’ve already spent time with React, vanilla JavaScript, and (just
now) Svelte, I wanted to pick something that is completely different – and
hopefully answer that “why not?” in producing a demo. As it so happens, I
came across an existing demo online – this creates a simple counter using
two buttons. I’ve adapted the original as a basis for my version, which we
will go through in the next demo.

118

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

FRAMEWORK EXAMPLE PART 2: ALPINE.JS

To build our demo in Alpine.js, follow these steps:

1. First, create a new folder called comparing-with-alpinejs,

at the root of our project folder.

2. inside this folder, add a new file – we have a good chunk of

code to go through, so let’s do it block by block, starting with

the headers:

<html>

<head>

<title>Testing Shadow DOM with Alpine.js</title>

<script

defer

src="https://cdn.jsdelivr.net/npm/

[email protected]/dist/cdn.min.js"

></script>

3. next, immediately below it, add this block of styles:

<style>
span { width: 32px; display: inline-block; text-

align: center; }

button { width: 32px; height: 32px; border:

none; border-

radius: 6px; background-color: #a9a9a9; color:

#ffffff;

</style>

</head>

119

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

4. we have one more block to add, which is the body –

immediately below that last block, add these lines:

<body>

<h2>Web Component Reactive State - Shadow DOM</h2>

<script src="./reactiveshadow.js"></script>

<my-reactive-shadow start="33"></my-

reactive-shadow>

<button @click="dec">-</button>

<span x-text="counter">55</span>
<button @click="inc">+</button>

</body>

</html>

5. Save the file as index.html, and close it. next, open a new

blank file – we need to add our button component. For this, add

the following code, starting with the opening event handler call,

a variable definition for our template, and the inner hTML for

that template:

document.addEventListener("alpine:init", () => {

const template = document.createElement("template");

template.innerHTML = `

<style>

span, button { font-size: 120%; }

span { width: 32px; display: inline-block; text-

align: center; }

button { width: 32px; height: 32px; border: none;

border-radius: 6px; background-color: blue;

color: #ffffff;

</style>
120

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

<button @click="dec">-</button>

<span x-text="counter"></span>

<button @click="inc">+</button>

`;

6. we need to add the class that will form our component, so

leave a line blank, then add this code:

class ShadowDOMCounter extends HTMLElement {

state = Alpine.reactive({

counter: this.hasAttribute("start")

? parseInt(this.getAttribute("start"))

: 0,

inc: this.inc.bind(this),

dec: this.dec.bind(this),

});

7. This next block is the constructor for our component:

constructor() {

super();

const shadow = this.attachShadow({


mode: "open"

});

shadow.appendChild(template

.content

.cloneNode(true)

);

Alpine.addScopeToNode(shadow, this.state);

Alpine.initTree(shadow);

121

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

8. The final block takes care of incrementing or decrementing our counter,


based on using the connectedCallback() function:

connectedCallback() {}

inc() {

this.state.counter++;

dec() {
this.state.counter--;

customElements.define("my-reactive-shadow",

ShadowDOMCounter);

});

9. Save the file as reactiveshadow.js, then close it.

10. go ahead and open index.html in your browser – if all is well, we should
see something akin to the screenshot shown in

Figure 4-5.

Figure 4-5. An example using Alpine.js

Yes – something lightweight at last! I make no bones about saying that, as I


have always been a staunch advocate of the KISS (keep it simple …

yes, you get the idea…) principle. Granted, Alpine was never designed to
work or compete with tools such as React, but sometimes “less is more,” I
always say….

122

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

But I digress. While this book isn’t about Alpine, we’ve still covered some
valuable features, so let’s take a moment to explore the code in more detail.

Understanding the Changes

I don’t know why, but when working with Alpine, I get mental images of a
beautiful Swiss mountainous landscape with fresh air. I guess Alpine is that
breath of fresh air, proving we don’t always need a heavyweight tool to
crack a nut!

But I digress – for this demo, we created two files: the first was index.html,
inside which we added a call to the Alpine framework before adding the my-
reactive-shadow component, with a start prop value of 33. We also added
buttons and the starting value but didn’t hook them up – there is a reason for
this, which I will come onto in a moment.

Next up, we created our Shadow DOM component – we created an

event listener method that kicks when Alpine initializes our component, in
this case, my-reactive-shadow. We first define a template variable, into
which we assign a set of styles for the two buttons and span we will use in
our component. We then add markup for our buttons and a span to display
the value on screen. Notice that we have some Alpine tags in place

– @click is the equivalent of onClick, and x-text will set the text value of
our span to the value of our count.

In the second part of our component, we create a ShadowDOMCounter

class. In this class, we first retrieve the value from the start property or set it
to zero if one is not provided. We also bind in two functions – inc and dec –
which we will use to increase or decrease our count value. We then have our
constructor() function, inside which we create and initialize an instance of
the Shadow DOM for our component. At the same time, we define the inc
and dec functions – these will adjust the count accordingly and are triggered
when clicking on our two buttons. We then rounded out the demo by
previewing the results in our browser, and you can see our component using
the Shadow DOM in Figure 4-6.

123
ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

Figure 4-6. Proof that our Alpine component is using the Shadow DOM

Okay – it’s time for a change: we’ve covered two examples of using the
Shadow DOM API in different frameworks, but there are two questions I
want to answer. The first is performance – what is it like when using the
Shadow DOM API? Of course, there will be a lot of factors at play that can
affect it; nevertheless, it’s still important to understand what it means for us.

Assessing Performance of the API

Performance. Speed. Sub-2 seconds.

These are all terms I’ve heard many times over the years when

developing code – trying to create something easy to maintain and

performant and doing what people expect can be challenging at times!

That all said, the issue of performance was brought home when I came
across an interesting article by Nolan Lawson, at https://nolanlawson.

com/2022/06/22/style-scoping-versus-shadow-dom-which-is-fastest/.

In it, he talks about how different style scoping methods can impact
performance, particularly when using the Shadow DOM API. His research
shows a mixed bunch of results, but that attribute scoping (in red; see Figure
4-7) is not the fastest method. What is interesting, though, is that scoping
inside a Shadow DOM component (yellow) isn’t always the fastest either, as
shown in Figure 4-7.

124

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

Figure 4-7. Performance tests of different style scoping methods (source:


Nolan Lawson, as quoted in text)

The results from his initial research proved to be a mixed bag – he had
comments from both ends of the spectrum! Such was the polar response that
Nolan redid his research: it showed that while using attributes was still slow,
classes scoped in a Shadow DOM component were still fast but marginally
slower than unscoped classes.

It does raise an interesting point – it’s not sensible to base choices on the
performance of the Shadow DOM on its own; we should also
factor into the mix aspects such as the styling engine of the browser, your
development paradigm, and requirements for encapsulation. Indeed,

according to Nolan – Firefox’s Stylo engine is so fast that if it were in every


browser, we wouldn’t even need to worry about performance! Just
something to think about when working with the Shadow DOM…. With

that question out of the way, let’s move on to the second question.

125

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

Will Web Components Replace Tools Such

As React?

That is a good question, if perhaps a little contentious – after all, they can
both be used to create components, even if the methodology might be
different!

Over the last few years, we’ve become accustomed to using tools such as
React, Vue, and Angular – all good tools in their own right. However, they
are somewhat heavyweight frameworks, and there is an increasing trend
toward using more native or lightweight tools. It’s one of the reasons I like
tools such as Svelte or Web Components – both are more lightweight than
the likes of React and do not require the same mindset you need to have
when using React. Both do not require us to write as much code as we might
find we have to do with React, but Web Components doesn’t have quite the
same level of documentation as React – at least not yet!

One of the best features of Web Components – at least for me – is

reusability: with Web Components, you can write a component that will
work anywhere, be that React, Svelte, Vue, or other libraries. You can’t do
that with React – we have to use it within React. Web Components may have
some way to go in terms of maturing, but you can’t beat real reusability that
it offers!
In short – do I think React may go? I have mixed feelings about it – in some
respects, web components are a bit limiting, at least in the types of values or
properties you can pass between each component. React is far more mature
but has been around longer than Web Components. Given

time, we could see frameworks die out when people see that more native
solutions can offer the same functionality but with less baggage. After all –
look at something like jQuery: for a time, it provided its own solution to
fixing an issue. However, now, it has deprecated APIs in its package to
support more native solutions directly in the browser. Who knows what
might happen…?

126

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

And Now for Something

Completely Different…

We’ve almost come to the end of our journey through the Shadow DOM

API – we’ve covered a lot of content, introduced you to the basics of the
API, and explored how we can use it in our projects.

However, when researching for this book, I came across a couple

of articles that piqued my interest – it made me wonder: Could either (or


both) be done using the Shadow DOM API? Now, before you say

anything (and yes, I know what’s coming!) – I did ask myself this question
rhetorically; you will understand why when I go through each subject:

• Building a tabbed Chrome extension: https://web-

highlights.com/blog/building-your-own-new-tab-

chrome-extension/
• Building my version of React: www.babbel.com/en/

magazine/build-your-own-react-episode-2

• Building a JavaScript framework that uses the Shadow

DOM API: https://mikeguoynes.medium.com/part-1-

build-your-own-js-framework-from-scratch-

f4e35d0dffa6

Yes – don’t say I didn’t warn you! These would fall way outside the scope of
what we could do in this book, but it made me wonder what could be
possible. For example, I know the middle article uses Virtual DOM –

there are some similarities with Shadow DOM, so it could potentially be


adapted to work with the API. The important thing here is that we’re not
limited to creating components – options are available further in the field,
and it’s up to us to explore what we want to do with the Shadow DOM API.

127

ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS

Summary

Well – as someone once said, “all good things must come to an end
sometime…”

Indeed – we have reached the end of our journey through the Shadow DOM
API. We’ve covered a lot of topics throughout this book, ranging from
getting familiar with the basic API features to creating components, adding
Shadow DOM support to an application, and more – hopefully, something
here will have piqued your interest!

Over the last few pages, we focused first on adding Shadow DOM
support to existing React components – not all are blessed with the API from
the get-go, so it’s helpful to know how to apply it where needed.

We then explored a couple of examples of different frameworks to see how


they implement the Shadow DOM API – in Svelte’s case, it was done
automatically, while with Alpine, we had to add it manually as we did with
React and vanilla JavaScript.

We then moved on to briefly exploring performance – much of this

came from work done by the developer Nolan Lawson, where he found

that although it wasn’t the fastest, Shadow DOM–scoped components and


applications were not too far behind! We then rounded out with a short
debate around whether the Shadow DOM API (and Web Components)

could ever replace tools such as React before finishing with a quick
postscript on a couple of articles about taking Shadow DOM API support
further afield.

Phew – there’s a lot there! We have reached the end of our journey through
the API; I hope you’ve enjoyed learning about it as much as I have enjoyed
writing this book and that there will be something you can use in your future
projects.

128

APPENDIX

API Reference

In this reference, we list the key interfaces, properties, and methods for the
Shadow DOM API – it’s worth noting that more details are available on the
MDN website; I’ve provided links as a starting point for each section.

Interfaces

The list of key interfaces is defined in Table A-1.


Table A-1. Main interfaces for the Shadow DOM API

Interface

Purpose

CustomElementRegistry Includes methods for registering and querying


custom elements

For more information, please see https://

developer.mozilla.org/en-US/docs/Web/

API/CustomElementRegistry

HTMLSlotElement

Provides access to the name and assigned nodes of a

<slot> element

For more information, please see https://

developer.mozilla.org/en-US/docs/Web/

API/HTMLSlotElement

( continued)

© Alex Libby 2024

129

A. Libby, Beginning Shadow DOM API, Apress Pocket Guides,

https://doi.org/10.1007/979-8-8688-0249-2

APPeNDIx API ReFeReNce

Table A-1. ( continued)


Interface

Purpose

HTMLTemplateElement

Provides access to the contents of an HTML

<template> element

For more information, please see https://

developer.mozilla.org/en-US/docs/Web/

API/HTMLTemplateElement.

ShadowRoot

The root Node.js of a DOM subtree created using

the Shadow DOM API, which is separate from the

document’s main DOM tree

For more information, please see https://

developer.mozilla.org/en-US/docs/Web/

API/ShadowRoot

Properties

The key properties for the Shadow DOM API are listed in Table A-2.

Table A-2. The list of key properties for the Shadow DOM API Property

Purpose

Element.shadowRoot
Represents the shadow root for the targeted element

For more information, please see https://

developer.mozilla.org/en-US/docs/Web/

API/Element/shadowRoot

Element.slot

Returns the name of the Shadow DOM slot into which

the target element is inserted

For more information, please see https://

developer.mozilla.org/en-US/docs/Web/

API/Element/slot

( continued)

130

APPeNDIx API ReFeReNce

Table A-2. ( continued)

Property

Purpose

Event.composed

A read-only Boolean property used to ascertain if the

event will propagate across the Shadow DOM into the

standard DOM
For more information, please see https://

developer.mozilla.org/en-US/docs/Web/

API/Event/composed

Event.composedPath

Returns the event’s path as an array of objects on

which listeners will be invoked

For more information, please see https://

developer.mozilla.org/en-US/docs/Web/

API/Event/composedPath

Node.isConnected

A read-only Boolean value to determine if the Node.js

is directly (or indirectly) connected to a Document

object

For more information, please see https://

developer.mozilla.org/en-US/docs/Web/

API/Node/isConnected

Window.customElements This read-only property returns a reference to the


customelementRegistry object to register new

custom elements and retrieve information about

previously registered custom elements

For more information, please seehttps://


developer.mozilla.org/en-US/docs/Web/

API/Window/customElements

131

APPeNDIx API ReFeReNce

Methods

The main methods for the API are listed in Table A-3.

Table A-3. The key methods for the Shadow DOM API

Method

Purpose

Document.createElement()

Used to create an HTML element using a

tagName if supplied or HTMLUnknownelement if

one is not recognized or provided

For more information, please see https://

developer.mozilla.org/en-US/docs/

Web/API/Document/createElement

Element.attachShadow()

Attaches a Shadow DOM tree to our chosen

element and returns a reference to its

ShadowRoot
Note that not all elements accept a Shadow

DOM tree, such as <a> – for the complete list

and more information, please see https://

developer.mozilla.org/en-US/docs/

Web/API/Element/attachShadow

Node.getRootNode()

Gets the context object’s root, including the

shadow root if one is available

For more information, please see https://

developer.mozilla.org/en-US/docs/

Web/API/Node/getRootNode

132

Index

A, B, C

testing

development process, 60

Alert dialog components, 36

Testing-Library/

component, 49

Cypress, 57–62
connectedCallback()

toggle switch creation, 42–49, 55

function, 42

Alpine.js

console log, 41

component, 123, 124

customElements.define()/

connectedCallback()

shadowRoot.

function, 122

appendChild(), 36

constructor() function, 123

demo constructing

demo online, 119–124

process, 51–56

index.html, 123

functions/event handlers, 50

Shadow DOM component, 123

markup, 36–40

Application programming
my-alert component, 40

interface (API)

publishing/packaging

commands, 17

component/update, 70, 71

component, 18

npm publish

desktop browser support, 19–21

commands, 68–71

methods/properties/

package.json file, 63–67

interfaces, 17

postscript, 71, 72

performance test, 124, 125

publishing process, 62

SEO, 90

switch component, 43, 55–57

styling frameworks, 76–83

template, 41, 49

web components, 16, 21


© Alex Libby 2024

133

A. Libby, Beginning Shadow DOM API, Apress Pocket Guides,

https://doi.org/10.1007/979-8-8688-0249-2

INDEX

D, E, F, G, H, I, J, K,

S, T, U, V, W, X, Y, Z

L, M, N, O, P, Q

Search engine

Declarative Shadow

optimization (SEO), 90

DOM (DSD), 30, 96

benefits, 96

Document object model (DOM)

Edge’s browser console log, 95

.attachShadow() method, 3

Google Search Console

“Hello World” program, 3–5

Tools, 91–93

React site, 5–9


hydration, 96

Real DOM, 2

implementation, 90, 91

Virtual DOM, 2

Microsoft’s Bing, 93–95

server-side rendering, 96, 97

tested page, 92

Shadow DOM API, 75, 127

React application

advantages, 22

checkbox component, 83

Alpine.js, 118–124

functional component

API, 16

packages, 89, 90

applying styles, 31–33

index.html, 85

bigger picture, 15, 16

Radix component, 86
browser’s console, 10

Vite development server, 87

composition

React components

component, 28, 29

App.jsx, 101

Declarative

attachShadow function, 105

Shadow DOM, 24

BasicButtons, 105

postscript, 30, 31

browser’s console, 103, 104

slots and templates, 24–28

Button.jsx, 101

customElements layer, 15

existing components, 99,

demo code, 13, 14

104, 105

disadvantages, 22, 23

index.html, 102
DOM, 2

production environment, 100

flattened page, 12

ShadowRoot.js, 102

high-level concepts, 10–13

Web components, 126

interfaces, 129, 130

134

INDEX

principles, 9

features, 76

properties, 130–132

frameworks/libraries, 80

React app, 83–90

predefined tags, 76

React components, 99

tailwind.config.js, 81, 82

schematic level, 11

Twind demo, 77–81

search engine
Svelte framework

optimization, 90

components, 106–118

Svelte, 106–118

rating web component, 115

Styling frameworks

steps, 107–117

compatibility work, 81

Web components, 126

135

OceanofPDF.com
Document Outline
Table of Contents
About the Author
About the Technical Reviewer
Acknowledgments
Introduction
Chapter 1: Getting Started
Exploring the Different DOM Types
Creating a Simple Example
Understanding What Happened
Some High-Level Concepts
Breaking Apart the Demo Code
Considering the Bigger Picture
Understanding Terminology Around the API
Creating the Component – A Process
Browser Support for the API
Practical Considerations for using the API
Composition Using Slots and Templates
Breaking Apart the Code Changes
Using Slots and Templates – A Postscript
Applying Styles – What's Different?
Summary
Chapter 2: Creating Components
Creating an Alert Dialog
Constructing the Markup
Understanding What Happened
Creating a Toggle Switch
Breaking Apart the Changes
Constructing the Demo
Breaking Apart the Code
Testing the Components
Understanding What Happened
Packaging the Component for Release
Breaking Apart the Code Changes
Publishing the Component – A Postscript
Summary
Chapter 3: Applying Shadow DOM API in Apps
Working with Styling Frameworks
Exploring the Code Changes
Mounting a React App Using Shadow DOM API
Understanding What Happened
Implications of SEO and the API
Comparing with Bing
Challenges with Server-Side Rendering – A Postscript
Summary
Chapter 4: Supporting Other Frameworks
Adding Shadow DOM API Support to React Components
Breaking Apart the Code Changes
Exploring Other Framework Examples
First Example: Using Svelte
Exploring the Code in Detail
Second Example: Exploring Alpine.js
Understanding the Changes
Assessing Performance of the API
Will Web Components Replace Tools Such As React?
And Now for Something Completely Different…
Summary
Appendix API Reference
Interfaces
Properties
Methods
Index

OceanofPDF.com

You might also like