- Published on
Building a simple modal with Lit
- Authors
- Name
- Mathias Hove
- @mathias_hove
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.
- Install live-server locally or globally
Global
npm install -g live-server
Local
npm install live-server
- 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.