Back to the Front-end: Exploring the Future of the Umbraco UI (Part 8 - Lit)
Tags: umbraco, frontend, lit, webcomponents
Published: 2022-10-21 07:27:30

When HQ announced that a new backoffice UI was on the cards, by far the biggest question on everyones mind was "what framework will it be built in?".

Well we can finally answer that question as the framework of choice is [Lit](https://lit.dev). Now in comparison to things like [React](https://reactjs.org/) or [Vue](https://vuejs.org/), Lit might seem rather lacking, but I think they key thing HQ have learnt from the Angular UI is to not put all your eggs in one basket, and to stay as lightweight as possible, and so Lit is a perfect fit for that methodology.

## About Lit
Lit is a simple library to help you build fast and lightweight web components.

At Lit's core is a web component base class that provides reactive state, scoped styles, and a declarative template system.

Fundamentally, it hides away all the boilerplate code that comes with Web Component creation, and provides some key abilities that most modern frameworks today are based around (ie, reactivity and templating) saving you a ton of code.

For everything else you can either leverage vanilla JS functionality (which is suprisingly capable these days) or import and use other libraries for those specific functionalities.

Where Angular attempted to take care of everything, with Lit, things are much more modular.

## Getting Started
### Defining
As a basic example we'll convert our Web Component that we originally defined in our Web Component post to be a Lit based Web Component. The initial structure would look something like this:

```javascript
import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('message-box')
export class MessageBox extends LitElement {
  ...
}

declare global {
  interface HTMLElementTagNameMap {
    "message-box": MessageBox;
  }
}
```

The key elements here are that we first import the classes and functions we are going to need from the Lit ES Modules and we then define a class that extends the base `LitElement`. `LitElement` itself extends a `ReactiveElement` base class which in turn extends the standard `HTMLElement`. It's these base classes that give us the useful base implementations provided by Lit.

We can also see that our class is decorated with the `@customElement` attribute which is a Lit decorator which automates registering our component with the `CustomElementRegistry` with the provided name. 

Lastly we see a secondary declaration for the interface `HTMLElementTagNameMap`. What this does is signals to TypeScript to tell it whenever it sees a call for `document.createElement('message-box')` that it should automatically type the returned instance as our `MessageBox` type, thus maintaining static type checking when using it's API.

### Reactivity
Lit provides two main types of reactive state, public reactive properties defined using the `@property` decorator, and internal reactive properties defined using the `@state` decorator.

```javascript
import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('message-box')
export class MessageBox extends LitElement {

  @property()
  kind = 'info';

  @state()
  protected _active = false;

  ...
}
```

### Rendering
Rendering a Lit web component is handled via a `render` function defined on our class. Initally this will get called when the web component is added to the page DOM, but will also get re-called whenever any of the reactive properties used inside the render function are updated. 

This ensures our UI stays up to date as our data changes without us needing to expressly update it.

```javascript
import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('message-box')
export class MessageBox extends LitElement {

  ...

  render() {
    return html`<div id="msg-box" class="msg-box--${this.kind}">
      <h3><slot name="title">Default Message Title</slot></h3>
      <p><slot name="body">Deafult Message Body</slot></p>
    </div>`;
  }
}
```

We write our components template as HTML inside a JavaScript [tagged template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) using Lit's `html` tag function.

Our template can make use of our Web Component properties as well as contain JavaScript expressions to dynamically render details based on these properties.

Properties defined using the `@property` decorator will automatically be exposed as Web Component properties/attributes, where as properties defined using the `@state` decorator will not be exposed publically.

As these are both reactive, any changes to these properties will automatically notify any code that is monitoring them for changes or trigger a re-render if used within the render function.

There is a lot you can do in a template so be sure to checkout the [templating docs](https://lit.dev/docs/templates/overview/) for more details.

### Styling
In our vanilla Web Component example we embedded the CSS for our component in it's markup template. In Lit we can expose our styles via a static `style` property on our class using the tagged template literal `css` function:

```javascript
import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('message-box')
export class MessageBox extends LitElement {

  static styles = css`#msg-box {
        padding: 10px;
        border-style: solid;
        border-width: 2px;
        border-radius: 5px;
        font-family: sans-serif;
      }
      #msg-box > h3 {
        margin: 0 0 10px;
        padding: 0;
        font-size: 16px;
      }
      #msg-box > p {
        margin: 0;
        padding: 0;
        font-size: 12px;
      }
      .msg-box--success {
        border-color: #059669;
        background-color: #a7f3d0;
      }
      .msg-box--warn {
        border-color: #eab308;
        background-color: #fef08a;
      }
      .msg-box--info {
        border-color: #2563eb;
      }`;

  ...
}
```

Similar to html templates, we can easily embed variables and expressions in our styles to generated computed styles.

### Events

#### Listening to Events
To listen for events you can either use the standard `addEventListener` API, or Lit also includes a declarative way to add event listeners within your templates.

```javascript
import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('message-box')
export class MessageBox extends LitElement {

  ...

  render() {
    return html`<div id="msg-box" class="msg-box--${this.kind}" @click="${this._handleClick}">
      <h3><slot name="title">Default Message Title</slot></h3>
      <p><slot name="body">Deafult Message Body</slot></p>
    </div>`;
  }

  private _handleClick(e: Event) {
    console.log("Clicked");
  }
}
```
#### Dispatching Events
Components can dispatch their own events using the standard `dispatchEvent` API.

```javascript
this.dispatchEvent(new CustomEvent('value-change', { bubbles: true, composed: true }));
```

### What about the Shadow DOM?
You may have noticed that so far we haven't mentioned or done anything with the shadow DOM. Well that's because by default all Lit components are set up with a shadow DOM to which the render function is applied and the styles attached.

If you need to access an element from the shadow DOM, components expose a `renderRoot` on which you can call standard query selectors.

## Our Complete Example Web Component
Checkout the CodeSandbox below for a fully working example of our Lit based Web Component built during this post.

{% embed https://codesandbox.io/embed/epic-rubin-77pzgb?fontsize=14&hidenavigation=1&theme=dark %}

## Conclusion
What I've blogged in this article is a really high level view and there is soooooo much more to Lit, but I think this should give you a simple intro to the major bits that you'll see used within the backoffice UI codebase.

## Additional Resources
- [Lit Docs](https://lit.dev/)