Skip to content

How to Share Styles in Web Components with LitElement and TypeScript

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. 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 to 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 of LitElement. This means the CorpButton 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 to super.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 by 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 import 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

Want to 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.