Getting Started with a New Lit Project in 2023

Learn how to build a web application using Lit

Fernando Doglio
Bits and Pieces
Published in
13 min readMay 8, 2023

--

Lit is a lightweight, fast, and modern Web Component library that enables developers to create framework-agnostic, reusable UI components with ease. However, getting started with Lit and integrating it into a new project can be challenging.

That’s where Bit comes in.

In this article, we will explore how to use Bit to create a new project with Lit and build scalable and reusable components efficiently.

Whether you are new to Lit or an experienced developer, this article will provide valuable insights and practical tips for getting started with Lit and Bit and creating high-quality UI components for your next project.

So, let’s dive in and explore this through the process of creating a brand-new To-Do app!

Getting started

The first thing we gotta do is create the project. We’ll be building a simple ToDo app with 3 components:

  • The actual application wrapper. This component will have a form with a single input field and a button. When we hit the button, a new element will be added to an internal list, and that list will be passed to the <todo-list> component.
  • The list of To-Dos. This component will render the list of tasks and it will also show a message when we don’t have anything on our list.
  • Every single To-Do item on the list. Finally, the items on the list of To-Dos will have a checkbox next to them, when checked the text of the task will be crossed out.

Here is what the application looks like:

Lit is going to be used (as you already know) for this, so make sure to install it with the following line:

npm install lit

That’s it, that’s all you need to start creating. However, we’ll also need something like Polymer to make this work, because without it, the moment you start importing your Lit-based web components, the browser won’t know how to resolve the Lit dependencies.

Use the following line to install it:

npm install --save-dev polymer-cli

You can then create the polymer.json file with the following content:

{
"entrypoint": "./index.html",
"shell": "./todoApp.js",
"npm": true
}

The following should be your project structure:

You can run the project with this line:

polymer serve --open-path ./index.html

Now that we know how to get started, let’s quickly create our Lit components.

Creating your first Lit component

Lit is a Google library that simplifies the process of building Web Components. This library gives you all the building blocks, but you need to have some understanding of how Web Components work.

💡 Read more about Web Components and how they work.

By now Lit should be installed in your project along with Polymer.

To create a Web Component with Lit you’ll have to create a new file, define a class inside it, make sure it extends the LitElement class and then use the window.customElements.define method to define a new custom element and associate it with your class.

The only other requirement, which comes from Web Components (and not from Lit), is that the custom element name should have the “-” character in the middle. In other words, you can’t define a one-word element, it should have, at least, two.

import { LitElement, html, css } from 'lit';

export class TodoApp extends LitElement {
/*... class definition here ...*/
}


window.customElements.define('todo-app', TodoApp);

Inside these classes, you’ll be able to define the custom properties (if any) and the mandatory render method, which is in charge of returning the HTML representation of the component.

Let’s take a look at the full TodoApp class:

import { LitElement, html, css } from 'lit';
import './components/TodoList.js'

export class TodoApp extends LitElement {
static properties = {
todos: { type: Array },
newTodo: { type: String }
}
constructor() {
super();
this.todos = [];
this.newTodo = '';
}
static styles = css`
/* ...styles here... */
`;
render() {
return html`
<h1>Todo App</h1>
<form @submit="${this.addTodo}">
<input type="text" placeholder="New Todo" .value="${this.newTodo}" @input="${this.updateNewTodo}">
<button type="submit">Add Todo</button>
</form>
<todo-list .todos=${this.todos}></todo-list>
`;
}
updateNewTodo(event) {
this.newTodo = event.target.value;
}
addTodo(event) {
event.preventDefault();
this.todos = [...this.todos, {task: this.newTodo, completed: false}];
this.newTodo = '';
}
}

We’re defining 2 properties:

  • The list of ToDos in the form of the todo property.
  • The new task being created, in the form of the newTodo property.

Also, the render method is returning the form we talked about before. Every time we update the content of the input field, we execute the updateNewTodo method. And when we submit the form, we’ll execute the addTodo method, which only takes care of adding the new element into the todos array. Nothing else. Through the assignment of this internal property to the todos property of the <todo-list> component, Lit will take care of reacting to changes on the list and update the UI accordingly.

This component can be used from the HTML page easily like this:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>To-Do App</title>
<script type="module" src="./todoApp.js"></script>
</head>
<body>
<todo-app></todo-app>
</body>
</html>

By importing the script like this, and thanks to polymer, the <todo-app> element will properly render the form as we saw on the render method of that class.

The list of ToDos is similar, here is the full code:

import { css, html, LitElement } from 'lit';
import './TodoItem.js';

export class TodoList extends LitElement {
static get properties() {
return {
todos: { type: Array },
};
}

static get styles() {
return css`
/* styles here */
`;
}

constructor() {
super();
this.todos = []
}

render() {
return html`
<h2>To-Do List</h2>
${this.todos.length === 0
? html`<div class="no-todos">You have no to-do items</div>`
: this.todos.map(
(todo) =>
html`<todo-item task=${todo.task} .completed=${todo.completed}></todo-item>`
)}
`;
}
}

customElements.define('todo-list', TodoList);

The most relevant part of this code is the render method. Notice how we have a state for when the list of Todos is empty and a different one for when we actually have items to render.

Finally, the <todo-item> component is very straightforward, like the previous one:

import { css, html, LitElement } from 'lit';

export class TodoItem extends LitElement {
static get properties() {
return {
task: { type: String },
completed: { type: Boolean },
};
}

static get styles() {
return css`
/* styles here */
`;
}

constructor() {
super();
this.task = '';
this.completed = false;
}

render() {
return html`
<div class="todo">
<input type="checkbox" .checked=${this.completed} @change=${this._handleCheckboxChange} />
<div class="todo-text ${this.completed ? 'completed' : ''}">${this.task}</div>
</div>
`;
}

_handleCheckboxChange(e) {
this.completed = e.target.checked;
}
}

customElements.define('todo-item', TodoItem);

The render method is providing you with the means to update the item’s internal state (the completed property). And when that property changes, then we update the UI accordingly.

Are we done?

The code works, but we can improve the entire workflow by adding Bit into it, so let’s take a look at that now.

Managing and sharing our components

By now we have the app working, but let’s just pretend this is version 0.1 and we want to evolve it as part of a team.

That means we’ll need a way to share the code, let others collaborate with it and most likely, version each component individually in case you’d also like to re-use them somewhere else.

That’s where Bit comes into play!

Bit, in case you haven’t heard about it yet, is a platform for collaborating on and sharing reusable code components. With Bit, you can break your codebase into small, self-contained components that can be easily shared and reused across multiple projects. This approach can greatly improve development speed and code quality, as developers can focus on creating small, modular components that can be used and tested independently.

Bit is particularly useful for front-end development, where there is a high degree of componentization. With Bit, developers can share and discover UI components, styles, and logic, making it easier to build complex user interfaces. Additionally, Bit provides a powerful workflow for managing and versioning components, allowing developers to collaborate on and maintain shared components over time.

👉 In this article, I’m going to assume you already have Bit installed and you’ve read the basics. If you don’t know how to get started yet, read this article.

Setting up the scope

Scopes in Bit are like groups or categories which you can put your components into.

While this step doesn’t have to be the first thing we do, it’s a good way to start, especially so if you’re thinking of sharing your components in the near future.

So let’s go to Bit.cloud (Bit’s platform for hosting and collaborating on components), let’s login (or sign-up if you haven’t already) and create a new scope by clicking on the “New” button located on the top-right corner of the page.

Hit the “Scope” option, and then make sure to create a personal scope named ‘lit-components’:

This will create an empty scope where we can start publishing our components.

Initializing the workspace

With the scope ready we can now run the following command to get the bit workspace up and running:

bit new lit my-workspace --env teambit.web-components/lit --default-scope deleteman.lit-components

The Bit workspace is essentially a development environment pre-configured with everything you need to create, build and publish your components. With the above line, we’ve created a new workspace pre-configured to work with Lit components. We also specified the default scope by adding my username and the name of the scope we defined in the previous step.

👉 If you’re doing this at home, remember to replace my username with yours.

It’s that simple, with that line, Bit created the required folders and the workspace.jsonc file which will control the configuration of the workspace.

Keep in mind that from now on, you’ll have to run your commands from within the my-workspace folder. You can use any name here, and that name will be used for the folder of the workspace.

Learn more:

Creating the components

With Bit you don’t have to create the components up-front, you can actually extract pieces of your code into components and then treat them as if they were packages installed through npm, even if they’re in fact located within the same folder. This is fantastic when you’re adding Bit to an existing project, that way you don’t have to start from scratch.

And that is exactly what we’re going to do.

Let me show you what I mean.

Let’s first create the components like this:

my-workspace> bit create lit todos/todo-list --aspect teambit.web-components/lit
my-workspace> bit create lit todos/todo-item --aspect teambit.web-components/lit

Those two lines will create two folders inside my-workspace/lit-components/todos the todo-list and todo-item folders.

And inside each of these folders, you’ll have a similar structure:

index.js # the main file exporting your component
<comp. name>.composition.ts # this file exports different versions of your component for the documentation
<comp. name>.docs.mdx # the file where you'll write the actual documentation
<comp. name>.spec.ts # here you'll write the tests
<comp. name>.ts #the actual component, we'll put our code in this file

We’ll only worry about the <comp. name>.ts files, because we’re going to put the original code from our components in there.

And the original files will simply disappear, because now whenever you need to import one of these components, you’ll use the new URL:

These are symlinks that Bit created for you, they now reference the local copy of your files, but you can import them as if they were npm packages you just installed. That way you can update your code to use “external” dependencies even before you publish them.

This means the import statement on the main todoApp.js file now changes to import '@deleteman/lit-components.todos.todo-list' . And internally, the <todo-list> import of the <todo-item> component also needs to be updated accordingly.

💡 Note that now that you’re importing the Bit components, you don’t really reference a JavaScript file, so your components could be fully written in TypeScript if you wanted and Bit will do the translation for you. Neat!

And after doing that, make sure to install any pending or missing dependency with bit install .

That should be more than enough to keep going.

Publishing your work

Now that you’ve created the new components, exported their code and updated the import statements accordingly, the only missing step to start collaborating with other people is to share them online.

For that, we’ll first have to version our components. This will ensure everyone is using the right version of the code and that there are no collisions when pushing changes from different places.

To do that, we’ll use the bit tag command, which will automatically assign a version number, compile and verify our components before they can be published.

bit tag deleteman.lit-components/todos/todo-item
bit tag deleteman.lit-components/todos/todo-list

Notice that I’m using my own username there, you’ll have to update it with your own.

Also, before you run those lines, make sure to update the test files for each of your components, as they will be executed during the tagging process.

If everything goes according to plan, you should see something like this:

The component has been tagged with version 0.0.1.

Now it’s ready to be published. And we can easily do that with a single command:

bit export

The export command will grab all the tagged components and export them into the Bit.cloud platform (provided you’ve already used the bit login command).

You should be seeing an output similar to this one:

Which means everything’s been correctly exported. In fact, this is how my components look like exported and live right now:

Reusing these components

As a final action, after you’ve successfully exported these web components into Bit Cloud, you can use them in your own projects.

How?

You actually have multiple ways, just click on one of them to see the options. On the top right corner, you’ll see a “Use” button:

You can see in the above screenshot all possible options.

You can go with a standard package manager like pnpm or Yarn. Or you can use Bit.

The Bit option is the most interesting one because you actually have different ways in which you can add the component to your project.

  • You can install the component, which would be the same as using npm or yarn. It’ll add the component as a dependency and you’ll be able to import it normally.
  • You can “import” the component. This will copy the files into your working folder but at the same time, it’ll create the same symlinks it created before. This allows you to import the files as if they were living inside the node_modules folder, but at the same time, you have easy access to them and can modify them to suit your specific needs.
  • And you can “fork” a component, which is similar to “importing” it, but you’ll have a fresh start with it. That means you won’t be overwriting any previous versions if you make changes, instead, they’ll be treated as new components. This is great if you want to modify a component and make it your own, but you don’t have write permissions on its scope.

In the end, the way you use them is up to you, the great thing is that you have options!

Learn More:

Creating Web Components with Lit is relatively easy once you understand how to serve them (through Polymer, Webpack or whatever you want to use).

But creating isolated components is not enough if you want to create a growing library of them, especially if you want others to use them and change them.

That is where Bit comes into play and provides the required features to make your life and your team’s life a lot easier. Not to mention, taking care of looking out for your code’s quality.

Have you used Lit before? What are your thoughts about it?

Build Apps with reusable components, just like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo

--

--

I write about technology, freelancing and more. Check out my FREE newsletter if you’re into Software Development: https://fernandodoglio.substack.com/