Published on

Building a simple modal with Lit

Authors

Using Lit to Build a Simple Modal Dialog

For almost every web application, you will eventually need a modal. For a login form, confirmation prompt or some kind of custom dialog. For demonstration purposes, i want to show you how easy it is to generate a modal web component that can be used in all your projects. It's encapsulated, dependency free and reusable across any framework. Even just plain html.

Let's go through this simple sample, that will highligt the power of Lit, and show how easy it is to get started.


1. The Component Code

Create a file (e.g., MyModal.js) and drop in the following component:

import { LitElement, html, css } from 'https://unpkg.com/lit@2.8.0/index.js?module'

export class MyModal extends LitElement {
  static properties = {
    open: { type: Boolean, reflect: true },
  }

  static styles = css`
    :host {
      position: fixed;
      inset: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      pointer-events: none;
      opacity: 0;
      transition: opacity 0.25s ease-in-out;
    }
    :host([open]) {
      pointer-events: auto;
      opacity: 1;
      background: rgba(0, 0, 0, 0.5);
    }
    .content {
      position: relative;
      background: white;
      padding: 1.5rem;
      border-radius: 0.5rem;
      min-width: 300px;
      max-width: 500px;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
      transform: translateY(-20px);
      transition: transform 0.25s ease-in-out;
    }
    :host([open]) .content {
      transform: translateY(0);
    }
    button.close {
      background: none;
      border: none;
      font-size: 1.25rem;
      position: absolute;
      top: 1rem;
      right: 1rem;
      cursor: pointer;
    }
  `

  constructor() {
    super()
    this.open = false
    this._onKeydown = (e) => {
      if (this.open && e.key === 'Escape') this.close()
    }
  }

  connectedCallback() {
    super.connectedCallback()
    document.addEventListener('keydown', this._onKeydown)
  }

  disconnectedCallback() {
    document.removeEventListener('keydown', this._onKeydown)
    super.disconnectedCallback()
  }

  close() {
    this.open = false
    this.dispatchEvent(new CustomEvent('close'))
  }

  render() {
    return html`
      <div class="content" role="dialog" aria-modal="true">
        <button class="close" @click=${this.close} aria-label="Close"></button>
        <slot></slot>
      </div>
    `
  }
}

customElements.define('my-modal', MyModal)

2. How It Works

  • Reactive property: The open boolean toggles visibility.\
  • CSS transitions: Overlay fades in/out, and modal content slides into view.\
  • Close options: Click the top-right "✕" button or press Escape to close.\
  • Scoped styles: Shadow DOM ensures encapsulation---your CSS stays inside the component.

3. Usage Example

You can add the component we have created directly into a HTML fil like this:

<!doctype html>
<body>
  <script type="module" src="./MyModal.js"></script>

  <my-modal id="modal">
    <h2>Hello from Lit!</h2>
    <p>This modal animates and supports ESC‑key close.</p>
  </my-modal>

  <button onclick="document.querySelector('#modal').open = true">Open Modal</button>
  <my-modal open>
    <h2>Hello World</h2>
    <p>This is a simple modal</p>
  </my-modal>
</body>

4. Why This Matters

Lit makes it trivial to create a modal that is:

  • Portable --- works in React, Vue, Angular, or plain JS
  • Dependency-free --- no external libraries required
  • Easy to maintain --- just one file with clean structure

You can drop it into any project without worrying about CSS collisions or script conflicts.

4. Why This Matters

With Lit, a modal is just:

  • Portable – use it in React, Vue, Angular, or vanilla JS
  • Dependency-free – no extra libraries to worry about
  • Clean – one file, simple structure

5. Avoiding CORS Errors in the sample: Use live-server

If you open the HTML file directly with file://, the browser might block module imports (CORS errors). The fix: run a small local dev server.

I use live server when i have this problem.

  1. Install live-server locally or globally

Global

npm install -g live-server

Local

  npm install live-server
  1. In your project folder, run:
live-server

This will open your project in the browser at a local address (like http://127.0.0.1:8080). When it is hosted locally, you won't get any CORS errors.

Have a go at it. Hope you like Lit as much as i do.