How to Share styles in Web Components with LitElement and TypeScript
Originally published at labs.thisdot.co
May 6, 2021 · 4 minutes read · Follow @luixaviles
Have you ever thought about the benefits of code reuse?
It is common to try to find techniques or strategies that allow us to save time and reuse existing code for the implementation of our applications. However, let’s start by first understanding what we mean by code reuse.
Code reuse, also called software reuse, is the use of existing software, or software knowledge, to build new software, following the reusability principles.
According to this definition, we may think of code blocks in different levels: development scripts, software components, testing suites, templates, styles, etc.
In this article, we’ll deep dive over the Web Components creation to understand how to reuse the styles through LitElement and TypeScript.
A Simple Web Component
Let’s get started creating a basic component to describe the main parts of it.
import { LitElement, html, customElement, css } from 'lit-element';
@customElement('corp-button')
export class CorpButton extends LitElement {
static styles = css`
.corp-button {
background-color: #e7e7e7;
color: black;
border: none;
border-radius: 4px;
padding: 15px 32px;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
}
.corp-button:hover {
background-color: #aaaaaa;
}
`;
render() {
return html` <button class="corp-button"><slot></slot></button> `;
}
}
In the above code snippet, there is a new class CorpButton
, which extends from the LitElement
base-class. However, one important part here is the use of the @customElement
decorator that allows us register our brand new component <corp-button></corp-button>
in a compact definition.
On other hand, the render
function returns the HTML content to be rendered by the component. This template is still using JavaScript notation since a template literal is being used via lit-html and that means it can include JavaScript expressions inside. How cool is that?
Also, pay attention to the styles defined using a tagged template literal(again) and the css
tag function. The styles
property has been defined using the static
keyword for performance reasons and it makes sense thinking it will be applied to a class level rather than every instance of it.
That’s all for this initial component, and it’s ready to be rendered using the following notation.
<corp-button>Click Me</corp-button>
Sharing Styles
Now, let’s suppose we’re building another web component and for some reason, we’re going to need the same styles. Then, it’s a good opportunity to apply the code reuse principles.
Style Inheritance
Inheritance is a core concept of any Object-Oriented Programming. Since we’re using TypeScript, we can take advantage of it derive a class from CorpButton
as follows.
import { html, customElement, css } from 'lit-element';
import { CorpButton } from './corp-button';
@customElement('corp-advanced-button')
class CorpAdvancedButton extends CorpButton {
static get styles() {
return [
super.styles,
css`
.corp-button:hover {
background-color: #008cba;
color: white;
}
`,
];
}
render() {
return html` <button class="corp-button"><slot></slot></button> `;
}
}
In the above example, we do the following:
- The new class extends from
CorpButton
(the previous class-component) instead ofLitElement
. This means theCorpButton
class can inherit attributes and behavior from its parent class. - Instead of defining a static property
styles
as the parent component does, it creates a static function instead to be able to perform a call tosuper.styles
and get access to its parent styles.
This is perfectly doable because the styles
property can be a tagged template literal or even an array of them.
static get styles() {
return [ css`...`, css`...`];
}
Style Modules
If you’re more in favor of implementing composition over inheritance, there’s another technique to share styles between components.
The first step is to identify the styles that need to be shared. Then, you can move forward creating a TypeScript module to contain the common styles.
// styles/styles-button.ts
import { css } from 'lit-element';
export const corpStylesButton = css`
.corp-button {
background-color: #e7e7e7;
color: black;
border: none;
border-radius: 4px;
padding: 15px 32px;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
}
.corp-button:hover {
background-color: #aaaaaa;
}
`;
To implement this module, we created a new folder /button/styles
. Then, the styles-button.ts
file contains the exported styles using, again, the tagged styles notation.
The previous definition can be imported in your new component as follows.
import { LitElement, html, customElement, css } from 'lit-element';
import { corpStylesButton } from './styles/styles-button';
@customElement('corp-advanced-button')
class CorpAdvancedButton extends LitElement {
static styles = [
corpStylesButton,
css`
.corp-button:hover {
background-color: #008cba;
color: white;
}
`,
];
render() {
return html` <button class="corp-button"><slot></slot></button> `;
}
}
In this case, we just imported the styles module and the styles
property gets updated with an array of tagged template literals:
static styles = [
corpStylesButton,
css`...`
];
This is a powerful feature since it can help us importing several styles of modules as required by the new component.
Please note the use of the styles
as a property and a method in the above examples.
Live Demo
Wanna play around with this code? Just open the Stackblitz editor:
Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work.