How to Create a LitElement Web Component — Heart/Unheart

Fionna Chan
The Startup
Published in
7 min readSep 5, 2020

--

Icons made by Freepik from www.flaticon.com

With React and Vue being the dominant players in the frontend field, most developers are reluctant to try out other frameworks (or libraries, the line is quite blurry with the current state of the web). I have always been a fan of framework-less approaches whenever possible and reasonable, so when Web Components debuted on Chrome, I was excited to play around with them without any frameworks to see what it was about. Needless to say, it was all very vanilla and hard to scale for production apps. I then sadly decided to end my affair with Web Components and stick to React or Vue.

By some chance, I got a job working with Polymer Web Components. To give some context, I am a frontend developer in love with building UI components in particular. It is such a niche in Hong Kong that there are probably only less than 20 companies in the whole market with a need for UI component specialists. After a lengthy search, my passion finally led me to my current job where I get to use Polymer and focus on UI components most of the time.

Working with Polymer brought me faith in Web Components once again. I am impressed by how elegant it is to use a more native approach to build web applications. Polymer has been around for several years by now, and their team has shifted focus to LitElement, so I thought it was time to start getting into LitElement.

I am sure most developers do the same when they try out new tools. We google. We stackoverflow. I did too.

It is only after some googling do I realise that I live on the bleeding edge:

A search of (“lit-element” OR “LitElement”) AND “component” showed about 30,400 results to date, whereas about 201,000,000 results showed up for (“React” OR “ReactJS”) AND “component”.

Web components are immensely underrated. I can see why it is not used in production for many reasons, one of them being not many developers are familiar with it, and another would be it being too bleeding edge and therefore unreliable as a long-term investment until it gains more popularity. However, it won’t do developers harm to learn how to build UI components with LitElement, and that is precisely why I am writing this article to introduce the beauty of it.

Today, we are going to create a heart toggle button. It is pretty standard for most web applications. Similar components are the star button, the hamburger menu button, and any kind of icon-based toggle buttons.

Getting Started

To begin, I highly recommend the JavaScript starter project and the TypeScript starter project on GitHub. In this article, we will use JavaScript instead of TypeScript to make things simpler. The prerequisites here are basic knowledge of JavaScript, Webpack/Rollup, and npm/yarn.

After you cloned the project starter to your computer via git, or download the zipped folder and unzipped it, you should run the following command to install the dependencies stated in package.json:

npm install
OR
yarn install

Once it is done, go ahead and run the server!

npm run serve
OR
yarn run serve

When the command has finished running, you should see this on your terminal.

es-dev-server started on http://localhost:8000

Using the starter project template available at the time of writing, http://localhost:8000 shows “Not Found”. Does that mean our app has failed to run? No, because if it is not running, you will see something like this:

Nothing is running here :(

Let’s have a look at the project folder structure and see where we should go.

The folder structure

Here we see an HTML file under the dev folder. It means that we can go to http://localhost:8000/dev/ and see this file.

Right now, there is the template starter content using <my-element></my-element>. We are going to duplicate my-element.js at the root folder to create our heart toggle button element. We will go back to the index.html under /dev/ once we have finished our component.

The first step is to rename the file to heart-toggle.js, all instances of MyElement to HeartToggle, and my-element to heart-toggle. If you are wondering why we name the element heart-toggle instead of heart, it is because a valid custom element name must contain at least one hyphen.

Setting up the Element

Then, let’s do these:

  1. Remove all the comments
  2. Remove all CSS inside :host {}
  3. Replace the HTML part in render() with <span></span>

Now your heart-toggle.js should contain the following code:

import {LitElement, html, css} from 'lit-element';export class HeartToggle extends LitElement {
static get styles() {
return css`
:host {
}
`;
}
static get properties() {
return {
name: {type: String},
count: {type: Number},
};
}
constructor() {
super();
this.name = 'World';
this.count = 0;
}
render() {
return html`<span></span>`;
}
_onClick() {
this.count++;
}
}
window.customElements.define('heart-toggle', HeartToggle);

To make development easier, let’s go to /dev/index.html on your IDE, add <script type="module" src="../heart-toggle.js"></script> within the head tag and add <heart-toggle></heart-toggle> below </my-element> so that we can see the changes made in the heart-toggle component instantly with hot reload.

Your /dev/index.html will look like this:

<!doctype html><html>
<head>
<meta charset="utf-8">
<title>&lt;my-element> Demo</title>
<script type="module" src="../my-element.js"></script>
<script type="module" src="../heart-toggle.js"></script>
<style>
p {
border: solid 1px blue;
padding: 8px;
}
</style>
</head>
<body>
<my-element>
<p>This is child content</p>
</my-element>
<heart-toggle></heart-toggle>
</body>
</html>

For those of you with experience in React and Vue, the code in heart-toggle.js should look fairly familiar. For those of you without any previous frontend framework experience, this should be much less overwhelming than it was initially. With this cleaner template, we will dive deeper into each part.

Styling the Element

Web components make use of custom elements and shadow DOM, the :host selector in CSS is targeting the shadow host of the custom element. Code in the shadow DOM is scoped within the element and will not be affected by global styles or document methods like getElementById() and querySelector() without a specific selection of the shadow root of the custom element. Read more about shadow DOM if you are interested.

Let’s add some CSS to our bland component.

:host {
--icon-size: 16vw;
display: block;
width: var(--icon-size);
height: var(--icon-size);
cursor: pointer;
}
span {
display: block;
width: 100%;
height: 100%;
background-image: url(https://image.flaticon.com/icons/svg/929/929559.svg);
background-size: 100% auto;
}
:host span::after {
content: "";
display: block;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-image: url(https://image.flaticon.com/icons/svg/929/929417.svg);
opacity: 0;
transition: opacity 0.4s;
}
:host([loved]) span::after {
opacity: 1;
}

In the :host, we are using a CSS variable to set the size of the icon. Using a CSS variable makes customization easier because you can target heart-toggle and set the variable size in global styles.

.homepage heart-toggle {
--icon-size: 4px;
}
.profile heart-toggle {
--icon-size: 2rem;
)

We mentioned earlier that the shadow DOM elements are scoped and cannot be targeted from the outside. It is true for both CSS targeting and JavaScript targeting. You can target the custom element itself, but nothing in the shadow DOM (in our case the <span></span>) with global CSS selectors, but you can still make all your customizations within the LitElement component and use properties to set what state and hence what styles the component should be showing.

I am using the pseudo-element ::after instead of another span or the same span for the filled heart icon because of the fading visual effect. You can tweak the DOM and styles for your usage.

As you can see from the above CSS, there is an attribute selector [loved] on heart-toggle. We need to add it as a property of the element and allow it to show as an attribute.

Adding a Property

static get properties() {
return {
loved: {
type: Boolean,
reflect: true,
}
}
}

The option reflect: true will set the attribute loved to each instance of the element heart-toggle when the property is true, and remove it when it is false for the instance of the component.

The final work for our component is to make the loved property toggle upon the user’s click.

constructor() {
super();
this.addEventListener("click", this._onClick);
}
_onClick() {
this.loved = !this.loved;
}

You can add all sorts of event listeners in the constructor, and handle the events with your event handlers in the way you would normally do with JavaScript.

Great job! You have created your first LitElement web component! 👏👏👏

If you haven’t had the time to set up the starter project on your computer, you can tweak my demo on CodePen to try it out.

Heart Toggle LitElement Demo on CodePen

The official guide of LitElement is a great resource for learning more about building web components. Do check it out! If you like this article, I will also try to write more tutorials on LitElement components. 😉

Tell me what you think below!

Follow me if you like this article. I would share my front-end experiments whenever I have time 😎

--

--

Fionna Chan
The Startup

Frontend Unicorn. I 💖 CSS & JavaScript. My brain occasionally runs out of memory so I need to pen down my thoughts. ✨💻🦄🌈.y.at