Beginning Shadow DOM API Get Up and Running - Alex Libby
Beginning Shadow DOM API Get Up and Running - Alex Libby
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.
or framework
industry
bookseries/17385.
Beginning Shadow
DOM API
Applications
Alex Libby
Beginning Shadow DOM API: Get Up and Running with Shadow DOM
for
Web Applications
Alex Libby
Belper, Derbyshire, UK
https://doi.org/10.1007/979-8-8688-0249-2
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.
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.
com/gp/services/source-code.
This is dedicated to my family, with thanks for their love and support while
writing this book.
Table of Contents
Summary33
vii
Table of ConTenTs
Summary72
Summary97
viii
Table of ConTenTs
Summary128
Index
������������������������������
������������������������������
������������������������������
�������133
ix
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
xiii
Acknowledgments
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!
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
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.
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....
https://doi.org/10.1007/979-8-8688-0249-2_1
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.
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 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:
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.
<div id="foo"></div>
3
CHapTeR 1 GeTTinG STaRTeD
'open'});
message.setAttribute('class', 'text');
shadow.appendChild(styles);
shadow.appendChild(message);
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
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.
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.
1. First, fire up a node.js terminal; then at the prompt, run this command:
2. npm will prompt you if Vite is not installed; select yes to install it if
needed.
npm install
5. Vite will fire up its development server – if all is well, you should see this
appear:
➜ Local: http://127.0.0.1:5173/
body {
font-family: sans-serif;
the code for our component. add this code, saving it at my-
customized- text.js:
import "./my-customized-text.css";
constructor() {
super();
"open" });
customElements.get("imperative-fancy-element") ||
customElements.define("imperative-fancy-element",
FancyElement);
our “site” – first, crack open App.jsx and replace the entire
function App() {
return (
<imperative-fancy-element></imperative-
fancy-element>
7
CHapTeR 1 GeTTinG STaRTeD
);
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
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
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.
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:
Shadow DOM.
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.
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.
10
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
Function
Shadow host
Shadow tree
Shadow
The place where the Shadow DOM ends and the regular DOM
boundary
begins
Shadow root
11
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
Shadow DOM, but only if you pass the appropriate prop! We’ll come
Okay – now that we’ve explored, let’s return to the code and break it apart to
see how it works in more detail.
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
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
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!
You will also notice that we used two different methods to initiate our
component – in the vanilla JavaScript demo, we inlined our code.
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
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
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
However, you will notice a box to the side of our pyramid – even
though these two features are part of the wider Web Components
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
Table 1-2. Some of the methods, properties, and interfaces of the API Term
Function
.attachShadow({mode: [
'open' | 'closed'] })
document.
createElement('style');
('class', 'text');
message.textContent =
'Hello World!';
shadow.appendChild()
all interfaces, properties, and events are listed in the appendix at the rear of
this book.
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
17
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:
HTMLElement class.
.attachShadow() method.
customElements.define() method.
answer. What is browser support like for this API? Is there anything we need
to consider, or will it ... just work?
18
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
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.
• The caniuse.com website has two versions of the API listed: v0 and v1. The
former, v0, has now been
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
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.
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).
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 –
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
With that in mind, let’s take a look at some of the advantages of using the
Shadow DOM API in more detail:
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:
22
process.
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
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.
rendering, mainly when using frameworks like React. The Shadow DOM
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.
To use slots with the Shadow DOM api, follow these steps:
vanilla-js.
2. inside this folder, crack open a new file and add this HTML
<!DOCTYPE html>
<html>
<head>
24
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>
<style>
p{
color: #000;
background-color: #ccc;
padding: 5px;
}
</style>
</slot></p>
</template>
<my-paragraph>
</my-paragraph>
25
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">
</ul>
</my-paragraph>
</body>
</html>
6. Save the file as index.html, then close it.
easier to view:
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',
constructor() {
super();
paragraph');
this.attachShadow({mode: 'open'}).appendChild(
templateContent.cloneNode(true)
26
CHapTeR 1 GeTTinG STaRTeD
);
);
paragraph span');
console.log(slottedSpan.assignedSlot);
console.log(slottedSpan.slot);
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
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
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.
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!
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
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.
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
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
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.
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-
caniuse.com/declarative-shadow-dom.
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
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.
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:
while the latter will keep the styles from leaking into
keep in mind!
31
odd effects!
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
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
35
A. Libby, Beginning Shadow DOM API, Apress Pocket Guides,
https://doi.org/10.1007/979-8-8688-0249-2_2
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.
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.
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
36
<!DOCTYPE html>
<html lang="en">
<head>
API</title>
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>
alert</my-alert>
</span>
<span>
alert</my-alert>
</span>
</body>
</html>
37
5. next, open a new file, then add this script block – we have
content to it:
template.innerHTML = `
<style>
:host {
display: block;
padding: 10px;
border-radius: 4px;
</style>
<div>
<slot></slot>
</div>
`;
6. This next part is where the magic happens – this turns our
"my-alert",
static is = "my-alert";
constructor() {
super();
38
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
Figure 2-1.
39
ChapTer 2 CreaTIng ComponenTs
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
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.
Figure 2-2. The compiled alert component, as shown in the console log
41
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
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.
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.
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:
new CustomEvent(CHANGED, {
detail: { checked },
});
43
3. next, leave a line blank, then add this block – this creates the styling (and
a placeholder) for our switch component:
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;
[part="slider"] {
width: 50%;
height: 100%;
background-color: #777777;
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
return [CHECKED_ATTR];
constructor() {
super();
template.content.cloneNode(true)
);
45
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.
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);
disconnectedCallback() {
this.removeEventListener("click", this.toggle);
this.removeEventListener("keydown", this._
onKeyDown);
this._updateChecked(true);
46
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;
}
};
switch (e.key) {
case "Enter":
e.preventDefault();
this.toggle();
break;
47
default:
break;
};
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);
folder, then close all files – we will create a demo for our new
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
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.
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
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_
49
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.
the demo from the original author of the toggle switch component – this
displays some text on-screen, which flips between two different styles.
50
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
<!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"
/>
</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>
<figure>
<p class="switch-container">
<label for="fancy-switch">
Fancy Switch
51
</label>
<toggle-switch id="fancy-switch"
class="fancy"></toggle-switch>
</p>
styling!</p>
</figure>
</section>
</main>
</body>
</html>
3. save the file as index.html and close the file. next, we need
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) => {
.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
a few rules, so let’s start with the ones needed to set up the
52
*,
*::before,
background: #ededed; }
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-size: 12px;
border-radius: 4px;
border: none;
color: #ffffff;
text-transform: uppercase;
letter-spacing: 1px;
cursor: pointer;
background: #468fdd;
button:hover,
::part(button):hover {
53
button:active,
::part(button):active,
button[disabled] {
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;
min-height: 150px;
}
justify-content: center; }
54
0.25); }
.fancy[checked]::part(track) { background-color:
#468fdd; }
should see something akin to that shown in Figure 2-3. Try switching it back
and forth to see what happens!
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.
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
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.
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.
<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) {
...
56
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….
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
This is important, as we will import the alert component directly from the
folder used in the previous demo to save copying it over.
components folder.
is set to /creating-component/testing-components.
press enter.
5. With the main testing framework now in place, let’s add the
6. Inside this folder, open a new file, then add this code:
testing";
describe("my-test", () => {
it("is accessible", async () => {
test</my-alert> `);
58
expect(el).to.be.accessible();
});
class="info"></my-alert> `);
expect(el.innerHTML).to.equal("");
});
class="info"></my-alert> `);
expect(el).to.have.class("info");
});
});
this output:
--node-resolve
__test__\alert.test.js:
Browser logs:
59
information.
Chrome: |█████████████████
passed, 0 failed
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!
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.
61
If you want to learn more about the testing project from open Web
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.
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.
involved:
npm can find and install it – we’ll use the alert dialog as
repository.
62
Getting-Started-Installing-Git.
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
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
open it in an editor and amend it so it looks like this, replacing XXXX with
your username for npm:
"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",
"scripts": {
64
"dev": "vite",
},
"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
65
ChapTer 2 CreaTIng ComponenTs
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
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.
component folder.
press enter.
3. npm will prompt you for your username, password, and email
67
prompt, and press enter – if all is well, you will see something
npm notice
component
component-0.1.0- alpha.tgz
nDr91vxi1BmVO[...]BoooaNydIVfaA==
npm notice
+ @shadowdombook/[email protected]
you can do this using the same command as before but append
68
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.
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
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!
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.
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.
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!
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:
71
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
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.
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
73
CHAPTER 3
Applying Shadow
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:
optimization (SEO)
75
https://doi.org/10.1007/979-8-8688-0249-2_3
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.
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:
justify-center">
<!— remainder of markup... -->
</main>
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
set the working folder for us. at the prompt, enter npm init -y,
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
<!DOCTYPE html>
<html lang="en">
<head>
</head>
77
6. next, open a new blank file and add this code – we’ll do it in blocks,
starting with the imports and opening statements:
customElements.define(
"twind-element",
(HTMLElement) {
constructor() {
super();
"open" });
shadow.innerHTML = `
items-center justify-center">
</h1>
</main>
`;
);
document.body.innerHTML = "<twind-element></twind-
element>";
78
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
config.js:
autoprefix";
/* config */
});
command prompt.
79
Chapter 3 applying Shadow doM api in appS
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.
Cast your mind back about 20 years. Do you remember a time when
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
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>
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
Let’s now also look at an example from the compiled styles in our
At first glance, this doesn’t look too dissimilar to standard CSS – but notice
the constructed stylesheet entry on the right.
82
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.
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?
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.
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
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
"name": "react-in-shadow-dom",
"version": "1.0.0",
"description": "",
"main": "src/index.jsx",
"keywords": [],
"author": "",
"license": "MIT",
"scripts": {
"start": "vite",
},
"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
3. next, open a new file, then add the following markup – save
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<div id="container">
and conditions:</span>
<div id="react-root"></div>
</div>
</body>
</html>
checkbox";
85
<form>
"center" }}>
<Checkbox.Root className="CheckboxRoot"
defaultChecked id="c1">
<Checkbox.Indicator className="CheckboxIndicator">
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Root>
</label>
</div>
</form>
);
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
@import "@radix-ui/colors/black-alpha.css";
@import "@radix-ui/colors/violet.css";
/* reset */
86
.CheckboxRoot:hover { background-color:
var(--violet-3); }
8. enter npm run start at the prompt and press enter to start
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
crack open the browser console and peer under the hood. the
Figure 3-5. Proof we're running the React app in 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
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.
89
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 –
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
there is usually a cost for this, using the free tier is fine
90
Okay – with the formalities out of the way, let’s move on to starting the
demo.
highlighting the various ways we can use the Shadow doM api.
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.
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.
to find out what google thinks of our site, follow these steps:
1. go to https://search.google.com/search-console,
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.
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
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…?
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.
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.
chosen method.
3. once done, click import from google Search Console, then hit
Continue.
93
5. next, click Url inspection, then enter the Url of your test
netlify site into the Url inspection field, and hit inspect.
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
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
“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!
A Postscript
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.
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.
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
96
Summary
One of the great things about using the Shadow DOM API (and, by
DOM API – for this, we used Tailwind as our example in the guise of Twind.
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!
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.
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.
99
https://doi.org/10.1007/979-8-8688-0249-2_4
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.
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.
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!
project folder.
100
3. Vite will prompt for some options – when prompted, choose the
following:
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 "./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:
101
return (
<Button variant="text">Text</Button>
<Button variant="contained">Contained</Button>
</Stack>
);
}
attachShadow(host) {
if (host == null) {
return;
host.shadowRoot.innerHTML = host.innerHTML;
host.innerHTML = "";
render() {
children}</span>;
102
<!DOCTYPE html>
<html lang="en">
<head>
vite.svg" />
</head>
<body>
<div id="root"></div>
</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.
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
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.
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
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?
105
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
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
for this, fire up a git Bash session, then make sure the working
• 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!
the first is our component, rate. Crack open a new file, then
<script>
107
let rate = 0;
let over = 0;
$: if (value) {
rate = convertValue(value);
over = convertValue(value);
val = length;
val = 0;
return val;
};
};
};
108
beforeRate(rate);
rate = index;
afterRate(rate);
};
};
arr.push(i);
};
beforeUpdate(() => {
if (arr.length === 0) {
createArray();
});
onMount(() => {
value = convertValue(value);
rate = convertValue(value);
over = convertValue(value);
});
</script>
109
<style>
div {
.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;
border: 0;
110
.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
line, then add this Svelte script. we’ll start with the initial
opening markup:
<div class="Rate">
<svg
width="0"
height="0"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<title>star-full</title>
<path
d="M32 12.408l-11.056-1.607-4.944
</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}
events -->
<button
type="button"
112
key={n}
class={'Rate__star'}
{disabled}>
<svg class="icon">
</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:
<span class="count">{over}</span>
{/if}
{/if}
</div>
</div>
{/if}
113
At this point, have a break for a moment – we’ve done the hard
<script>
</script>
<style>
</style>
<my-rating
length={5}
showCount={true}>
</my-rating>
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
Figure 4-3, where i’ve already selected the normal setting or three stars.
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.
115
ChApTEr 4 SuppOrTing OThEr FrAMEwOrkS
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.
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…?
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.
116
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()
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-
For the last part of the component, we set up the markup for each star –
117
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!
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.
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
the headers:
<html>
<head>
<script
defer
src="https://cdn.jsdelivr.net/npm/
[email protected]/dist/cdn.min.js"
></script>
<style>
span { width: 32px; display: inline-block; text-
align: center; }
none; border-
#ffffff;
</style>
</head>
119
<body>
<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", () => {
template.innerHTML = `
<style>
align: center; }
color: #ffffff;
</style>
120
<button @click="dec">-</button>
<span x-text="counter"></span>
<button @click="inc">+</button>
`;
state = Alpine.reactive({
counter: this.hasAttribute("start")
? parseInt(this.getAttribute("start"))
: 0,
inc: this.inc.bind(this),
dec: this.dec.bind(this),
});
constructor() {
super();
});
shadow.appendChild(template
.content
.cloneNode(true)
);
Alpine.addScopeToNode(shadow, this.state);
Alpine.initTree(shadow);
121
connectedCallback() {}
inc() {
this.state.counter++;
dec() {
this.state.counter--;
customElements.define("my-reactive-shadow",
ShadowDOMCounter);
});
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.
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
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.
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.
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.
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.
These are all terms I’ve heard many times over the years when
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
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,
that question out of the way, let’s move on to the second question.
125
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!
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
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.
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:
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
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 –
127
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.
came from work done by the developer Nolan Lawson, where he found
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
Interface
Purpose
developer.mozilla.org/en-US/docs/Web/
API/CustomElementRegistry
HTMLSlotElement
<slot> element
developer.mozilla.org/en-US/docs/Web/
API/HTMLSlotElement
( continued)
129
https://doi.org/10.1007/979-8-8688-0249-2
Purpose
HTMLTemplateElement
<template> element
developer.mozilla.org/en-US/docs/Web/
API/HTMLTemplateElement.
ShadowRoot
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
developer.mozilla.org/en-US/docs/Web/
API/Element/shadowRoot
Element.slot
developer.mozilla.org/en-US/docs/Web/
API/Element/slot
( continued)
130
Property
Purpose
Event.composed
standard DOM
For more information, please see https://
developer.mozilla.org/en-US/docs/Web/
API/Event/composed
Event.composedPath
developer.mozilla.org/en-US/docs/Web/
API/Event/composedPath
Node.isConnected
object
developer.mozilla.org/en-US/docs/Web/
API/Node/isConnected
API/Window/customElements
131
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()
developer.mozilla.org/en-US/docs/
Web/API/Document/createElement
Element.attachShadow()
ShadowRoot
Note that not all elements accept a Shadow
developer.mozilla.org/en-US/docs/
Web/API/Element/attachShadow
Node.getRootNode()
developer.mozilla.org/en-US/docs/
Web/API/Node/getRootNode
132
Index
A, B, C
testing
development process, 60
Testing-Library/
component, 49
Cypress, 57–62
connectedCallback()
function, 42
Alpine.js
console log, 41
customElements.define()/
connectedCallback()
shadowRoot.
function, 122
appendChild(), 36
demo constructing
process, 51–56
index.html, 123
functions/event handlers, 50
markup, 36–40
Application programming
my-alert component, 40
interface (API)
publishing/packaging
commands, 17
component/update, 70, 71
component, 18
npm publish
commands, 68–71
methods/properties/
interfaces, 17
postscript, 71, 72
publishing process, 62
SEO, 90
template, 41, 49
133
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
benefits, 96
.attachShadow() method, 3
Tools, 91–93
Real DOM, 2
implementation, 90, 91
Virtual DOM, 2
tested page, 92
React application
advantages, 22
checkbox component, 83
Alpine.js, 118–124
functional component
API, 16
packages, 89, 90
index.html, 85
Radix component, 86
browser’s console, 10
composition
React components
component, 28, 29
App.jsx, 101
Declarative
Shadow DOM, 24
BasicButtons, 105
postscript, 30, 31
Button.jsx, 101
customElements layer, 15
104, 105
disadvantages, 22, 23
index.html, 102
DOM, 2
flattened page, 12
ShadowRoot.js, 102
134
INDEX
principles, 9
features, 76
properties, 130–132
frameworks/libraries, 80
predefined tags, 76
React components, 99
tailwind.config.js, 81, 82
schematic level, 11
search engine
Svelte framework
optimization, 90
components, 106–118
Svelte, 106–118
Styling frameworks
steps, 107–117
compatibility work, 81
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