Rafa Romero Dios
5 Oct 2022
•
7 min read
Here we are with a new issue of our Web Components' series. It is always a good idea to make a summary of what we have seen so far in order to get acquainted with what this new chapter brings.
Up until now we tried to create a solid foundation about what a web component is as well as we have seen a part of its API, specifically the one related to how to define it, together with the attributes, props and probably the most important one: ShadowDOM and LightDOM.
In the last article we saw web components lifecycle hooks, as well as how to manage and trigger events.
So far we’ve worked on the theoretical parts of the API and it's time to kick-start the practice. So let’s see how to style web components, based on the following topics:
:host
:host-context
:slotted
selector:parts
selectorWe’ve got you covered with plenty of examples for every way of style web components. The examples are ready to copy paste straight into your code editor.
Let’s begin with a web component that you can use as a template. It is called <my-wc>
, and you can modify it according to the given styling way:
const template = document.createElement('template');
template.innerHTML = `
<style>
:host{
border: 3px solid yellow;
}
.container {
padding: 8px;
border: 2px solid;
}
h1{
border: 2px solid red;
}
p{
border: 2px solid blue;
}
.content{
border: 2px solid green;
height: 200px
}
</style>
<div class="container">
<h1>This is the title</h1>
<p>This is the text</p>
<button>I am a button</button>
<div class="content">
This is the content of the web component
</div>
<slot></slot>
</div>
`;
class MyWebComponent extends HTMLElement {
constructor() {
super();
this._shadowRoot = this.attachShadow({ mode: 'open' });
this._shadowRoot.appendChild(template.content.cloneNode(true));
}
}
window.customElements.define('my-wc', MyWebComponent);
So let's see what styling web components looks like!
:host
selector and web compnent's name selectorThe :host
selector is the one that points directly to the web component. In our case, we will be pointing to <my-wc>
. That means that every style that we define in this selector will be affected by the web component as a container or wrapper of the HTML.
It is thought to be used by the author of the web component, not the consumer. If we, as a web component consumer, want to style a web component (we refer to its container or wrapper as said before), we can use the web component's tag or name and it will end up with the same result.
So, the two following snippets of code will provoke the same result on the web component:
Here it is, defined by the author of the web component:
:host{
border: 1px solid;
}
Defined by the consumer of the web component:
my-wc{
border: 1px solid;
}
:host
selector can also be used along with another selector, defined within a parenthesis. This way is for exclusive use of the author of the web component. It can come in handy when styling the component depending on some attribute or class, for instance:
We apply styles to the component when it has an attribute called disabled
:
:host([disable]){
cursor: not-allowed
}
We apply styles to the component when it has a class called error
:host(.error){
border: 1px solid red;
}
:host-context
selectorHowever it's important to note that the :host-context
selector is for exclusive use of the author of the web component.
This selector allows us to apply a style to the web component depending on the context it is used in.
For instance, if we want to set some style (for example, a margin) only when our component is used inside an article
tag, we will define as follows:
:host-context(article){
margin: 10px;
}
:slotted
selectorThe :slotted
selector is the one that allows us to style those elements placed inside a slot. This only works when used inside CSS placed within a shadow DOM. It's important to notice that ::slotted(x)
targets the lightDOM outer-Element (aka 'skin'), NOT the SLOT in shadowDOM
Here are some basic examples:
/* Selects any element placed inside a slot */
::slotted(*) {
font-weight: bold;
}
/* Selects any <span> placed inside a slot */
::slotted(span) {
font-weight: bold;
}
The selector that can be used inside the parentheses only supports Basic selectors, although we can join some pseudo-selectors and pseudo-elements as you can see in the following example:
With the selector below, we will style any element that is placed in the slot that it is not a span
::slotted(*):not(span){
background-color: yellow;
}
With the selector below, we will style the :after
element of the span that is placed in the slot
::slotted(span):after{
content: 'after content';
}
With the selector below, we will style the placeholder pseudo-element of the input that is placed in the slot
::slotted(input)::placeholder{
color:# 666666;
}
CSS parts is the last API (not really an API but just a way to call it) that has been added to the Web Components ecosystem. The ::part
CSS pseudo-element represents any element within a shadow tree that has a matching part attribute.
That means that to use the CSS parts feature we need to define first a part
in our Web Components shadow DOM and later we will be able to style this section defined by the part
from the page where we are using the web component.
Let's see an example:
First of all, as mentioned before, we need to define a part inside our web component. To do that, we will base our web component in the template that we saw at the top of this article:
const template = document.createElement('template');
template.innerHTML = `
<div class="container">
<h1 part="header">This is the title</h1>
<p part="subtitle">This is the text</p>
<button>I am a button</button>
<div part="content" class="content">
This is the content of the web component
</div>
<slot></slot>
</div>
`;
class MyWebComponent extends HTMLElement {
constructor() {
super();
this._shadowRoot = this.attachShadow({ mode: 'open' });
this._shadowRoot.appendChild(template.content.cloneNode(true));
}
}
window.customElements.define('my-wc', MyWebComponent);
Then, from the page we are using our web component, we can use the ::part
selector as follows:
my-wc::part(header) {
color: blue;
}
my-wc::part(subtitle) {
color: black;
}
my-wc::part(content) {
color: grey;
}
CSS vars are probably one of the most powerful features of the "CSS API" released in the last few years. Although they are not defined to be used exclusively in Web Components, that is an important way (probably the most important one along with ::part
) to style Web Components.
The most common feature that is implemented with CSS variables and web components is theming, including light/dark themes.
Property names that are prefixed with --
, like --example-name
, represent custom properties that contain a value that can be used in other declarations using the var() function. Custom properties are scoped to the element(s) they are declared on, and participate in the cascade: the value of such a custom property is that from the declaration decided by the cascading algorithm.
The var() CSS function can be used to insert the value of a custom property (sometimes called a "CSS variable") instead of any part of a value of another property.
Although it's not mandatory, we can define a fallback for use when the property has not been set
So, basically we have three parts: defining the CSS var, using the CSS var and overriding the CSS var.
/* Defining de CSS var: */
--my-var-color: red;
/* Using the CSS var */
header{
color: var(--my-var-color, blue);
}
/* Overriding the CSS var */
--my-var-color: green;
Once we have been the theory of CSS var, let's see a practical example related to web components
Let's suppose we have the following web component:
<style>
header{
.content{
color: var(--content-color)
}
}
</style>
<div class="container">
<h1>This is the title</h1>
<p>This is the text</p>
<button>I am a button</button>
<div class="content">
This is the content of the web component
</div>
<slot></slot>
</div>
Then, from the page we are using our web component, we can override the CSS var as follows:
my-wc{
--content-color: blue;
}
Last but not least, we are going to see a way of styling web components that does not belong to the Web Component's ecosystem but is equally useful. We are talking about styling using JavaScript.
To style using JavaScript there is a sine qua non condition to be able to do it: We need an open Shadow DOM. Let's do a quick reminder about what open Shadow Dom means.
There are two types (modes) of Shadow DOM:
open
: Elements of the shadow root are accessible from JavaScript outside the root, for example using Element.shadowRoot
closed
: Denies access to the node(s) of a closed shadow root from JavaScript outside it. Not commonly used.So, if the web component's ShadowDOM is not open, we will not be able to access the web component's elements in order to style them.
So, imagine we have an open web component with the following HTML:
<div class="container">
<h1 class="header">This is the title</h1>
<p>This is the text</p>
<button>I am a button</button>
<div class="content">
This is the content of the web component
</div>
<slot></slot>
</div>
We can style, for instance, the colour of the header as follows:
const webComponent = document.querySelector('my-wc');
const header = webComponent.shadowRoot.querySelector('.header');
header.style.color = blue;
Now we’re one step closer to developing a Web Component. The next chapter will be on the libraries we have available to create Web Components, and in the following chapter we will create one by using the standard API.
Don’t forget - there are plenty of articles for you to recap, so help yourself and stay tuned for more.
Bibliography:
Rafa Romero Dios
Software Engineer specialized in Front End. Back To The Future fan
See other articles by Rafa
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!