Lit
01 / 02

Components, Templates & Reactive Properties

Lit: Components, Templates & Reactive Properties

Lit is a lightweight library (Google) for building standards-based Web Components -- real Custom Elements that work in any framework or none at all, since the browser itself understands them natively. Templates use plain JavaScript tagged template literals, no JSX compile step required.

A Basic Component

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

@customElement('my-counter')
export class MyCounter extends LitElement {
  // Scoped to this component's Shadow DOM -- won't leak out or
  // be affected by page-level CSS
  static styles = css`
    button { font-size: 1rem; padding: 4px 12px; }
    .count { font-weight: bold; }
  `;

  // Public reactive property -- reflects to/from an HTML attribute
  @property({ type: Number }) startAt = 0;

  // Internal reactive state -- not exposed as an attribute
  @state() private _count = 0;

  connectedCallback() {
    super.connectedCallback();
    this._count = this.startAt;
  }

  render() {
    return html`
      <p>Count: <span class="count">${this._count}</span></p>
      <button @click=${this._increment}>+1</button>
    `;
  }

  private _increment() {
    this._count++; // setting a reactive property schedules an efficient re-render
  }
}

// Usage anywhere -- React JSX, Vue templates, or plain HTML:
// <my-counter start-at="5"></my-counter>

Binding Syntax

html`
  <!-- Plain attribute -- serializes to a string -->
  <div id=${this.elementId}></div>

  <!-- ?attr -- boolean attribute, present/absent based on truthiness -->
  <button ?disabled=${this.isLoading}>Submit</button>

  <!-- .prop -- sets the DOM PROPERTY directly, bypassing attribute
       serialization -- important for form inputs where property and
       attribute can diverge -->
  <input .value=${this.text} />

  <!-- @event -- declarative event listener, Lit manages
       addEventListener/removeEventListener as the template updates -->
  <button @click=${this._onSave}>Save</button>
`;

Directives

import { repeat } from 'lit/directives/repeat.js';
import { classMap } from 'lit/directives/class-map.js';

render() {
  return html`
    <!-- Efficient KEYED list rendering -- like React's key prop -->
    <ul>
      ${repeat(this.items, (item) => item.id, (item) => html`
        <li class=${classMap({ active: item.id === this.selectedId })}>
          ${item.name}
        </li>
      `)}
    </ul>
  `;
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free