Component Styling For Fun and Profit

Search for a command to run...

I appreciate your effort in covering web components in such detail. Thanks again 🤩 🤩
I've begun developing web components (hasn't everyone, now?). This series explores the design decisions, code, lessons learned, and whatever else comes to my mind. You've been warned.
This is (probably) the final installment in my series on Web Components, which you should totally check out if you haven't already, not only because I wanted to use the phrase "totally check out" in print, but also because those previous couple of ar...
This is the second in a series of articles about building DevPail, my general-purpose development environment tooling tool. You don't have to read the previous article to learn something here, but the context may help you understand what I'm talking ...

This is the first in a series of articles about building DevPail, my general-purpose development environment tooling tool. Days gone by Oh, how I long for the days of yore. Especially Tuesdays. Tuesdays of yore were the best. Back then, building sof...

Fact is, I should not have built this code. I'm sure that somewhere, there's a suitable NPM or PyPi package (or something in some other language) which would have worked out just fine. But nothing I saw on the first page of my search results seemed l...

This is (probably) the final installment in my series on Web Components, which you should totally check out if you haven't already, not only because I wanted to use the phrase "totally check out" in print, but also because those previous couple of ar...

In the continuing saga of our Web Component building journey, we now turn our attention to allowing our web components to be styled. In case you missed it, we've been building a "loading modal" web component, and we've added some customizability via slots, properties and attributes already.
This component gives us a nice, simple way to drop a commonly-used feature into a web page. Load the component via <script> tag, add a <eh-loading-modal> tag somewhere on the page, and when needed, call the .show() and .hide() methods on the node.
What's still missing (and was promised in the previous article) is the ability to change the look of the component.
Before we begin, let's review the inline-CSS of our component.
this.shadowRoot.innerHTML = `
<style>
#loading {
background-color: gray;
height: 100%;
left: 0;
opacity: 0;
position: fixed;
top: 0;
visibility: hidden;
width: 100%;
z-index: 1;
}
#loading.visible {
opacity: 97%;
visibility: visible;
transition: visibility 0s linear 0s, opacity .25s 0s;
}
#loading .message {
height: 100%;
margin: 20px 2px;
padding: 2px 16px;
position: relative;
text-align: center;
top: 40%;
vertical-align: middle;
}
</style>
...
There are several hard-wired assumptions here about how the modal will look. It starts right at the top, with background-color: gray; and continues on with opacity: 97% under the .visible selector. These two lines give us the "mask" which ocludes the page when the component is visible. But what if we didn't want a 97% gray mask? What if we wanted sea-foam green?
The first thing you'd likely think of is something of this sort:
eh-loading-modal > div {
background-color: rgb(113 238 184);
}
Alas, this won't work. At least not as you expect, but more on that below. You may recall from the first article in this series that a component has a "mini-DOM" (shadowRoot) which sits inside the document's DOM. The issue here is that CSS selectors don't cross those boundries. Which makes sense, if you consider that our component is using IDs and class names for it's own purposes, and things could break badly if some arbitrary CSS from the page changed how our component worked, or we had multiple different components on the page, each with CSS which interferred with each other and/or the page itself. So 10 out of 10 to the web standards folks on that.
So how do we do it? More properties? Slots? Pan-fried salmon?
Nope, we'll leverage the fact that CSS custom properties DO cross those DOM borders!
Let's begin with our mask color:
this.shadowRoot.innerHTML = `
<style>
#loading {
background-color: var(--mask-background-color, gray);
...
}
#loading.visible {
opacity: var(--mask-opacity, 97%);
...
}
</style>
...
The var() function is replaced with EITHER the value of the named custom property, if it's defined, or the value after the comma, if not. Because we've put our original values in as fallbacks to the custom properies, nothing will (yet) change in our POC page, which, you'll recall, is:
<html>
<head>
<script src="eh-loading-modal.js"></script>
</head>
<body>
<main>some content here</main>
<eh-loading-modal></eh-loading-modal>
<script>
const loader = document.getElementsByTagName('eh-loading-modal')[0]
loader.show()
setTimeout(() => loader.hide(), 5000)
</script>
</body>
</html>
Let's sea-foam that baby up by adding a <style> block to the <head> of our page
<html>
<head>
<script src="eh-loading-modal.js"></script>
<style>
eh-loading-modal {
--mask-background-color: rgb(113 238 184);
--mask-opacity: 88%;
}
</style>
</head>
...
</html>
Reload the POC and bask in the glorious customization!
slotAbove, I suggested that a selector like eh-loading-modal > div { would work, but not as expected. If you are expecting that selector to pick up the first child div INSIDE the component's shadowRoot, you will be disappointed; selectors don't cross that boundry. This is why we need to use CSS custom properties above.
However, if you put content between the start and end tags of the component (in one or more slots), THOSE nodes ARE selectable via CSS. To see this in action try the following in your POC page:
<html>
<head>
<style>
eh-loading-modal > div {
color: blue;
}
...
</style>
</head>
<body>
...
<eh-loading-modal>
<div>This will be styled</div>
</eh-loading-modal>
...
</html>
Styles applied to slots by the component's CSS will be overridden by the page's CSS, so adding custom properties to slotted elements isn't strictly neccessary.
There are several ways to apply CSS rules to components and slots within components, including in a separate css file, in a <style> tag in either the <head> or the <body> or, interstingly, inside the component's tag.
Consider this:
<html>
<head>
<script src="eh-loading-modal.js"></script>
<style>
eh-loading-modal {
--mask-background-color: rgb(113 238 184);
--mask-opacity: 88%;
}
</style>
</head>
<body>
<main>some content here</main>
<eh-loading-modal>
<style>
* {
--content-text-align: right;
}
</style>
<div>This will be styled</div>
</eh-loading-modal>
<script>
const loader = document.getElementsByTagName('eh-loading-modal')[0]
loader.show()
setTimeout(() => loader.hide(), 5000)
</script>
</body>
</html>
So now we have a component we can customize, interact with and style. Next time out, we'll discuss event handling, both internally to a component and between the component and the page.