preline
Version:
Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.
380 lines (280 loc) • 17.6 kB
Markdown
# Overlay
The Overlay Tailwind plugin is an unstyled JavaScript utility for creating modals, drawers, and more, enhancing user interaction and content layout.
[](https://www.npmjs.com/package/@preline/overlay) [](https://opensource.org/licenses/MIT) [](https://preline.co/plugins/overlay.html)
## Contents
- [Overview](#overview)
- [Installation](#installation)
- [Basic usage](#basic-usage)
- [Configuration Options](#configuration-options)
- [JavaScript API](#javascript-api)
- [Events](#events)
- [Common Patterns](#common-patterns)
- [License](#license)
## Overview
The Overlay component provides a flexible foundation for creating modals, drawers, sidebars, and other overlay-based UI elements. It handles backdrop management, focus trapping, keyboard navigation, and z-index stacking automatically.
**Key Features:**
- Modal and drawer support
- Automatic backdrop management
- Focus trapping and keyboard navigation (Tab, Esc)
- Z-index stacking for multiple overlays
- Responsive breakpoint support
- Auto-close on screen size changes
- Programmatic control via JavaScript API
- Event system for lifecycle hooks
- Accessibility attributes (ARIA) built-in
## Installation
To get started, install Overlay plugin via npm, else you can skip this step if you are already using Preline UI as a package.
```bash
npm i @preline/overlay
```
### CSS
Use [`@source`](https://tailwindcss.com/docs/functions-and-directives#source-directive) to register the plugin's JavaScript path for Tailwind CSS scanning, then [`@import`](https://tailwindcss.com/docs/functions-and-directives#import-directive) the plugin's CSS files into your Tailwind CSS file.
```css
@import "tailwindcss";
/* @preline/overlay */
@source "../node_modules/@preline/overlay/*.js";
@import "./node_modules/@preline/overlay/variants.css";
@import "./node_modules/@preline/overlay/theme.css";
```
### JavaScript
Include the JavaScript that powers the interactive elements near the end of your `</body>` tag:
```html
<script src="./node_modules/@preline/overlay/index.js"></script>
```
### Manual Initialization
Use the `non-auto` entry if you need manual initialization. In this mode, automatic initialization on page load is not included, so the component should be initialized explicitly.
```html
<script type="module">
import HSOverlay from "@preline/overlay/non-auto.mjs";
new HSOverlay(document.querySelector("#overlay"));
</script>
```
### Via Bundler
When using a bundler (Vite, webpack, etc.), import the plugin directly as an ES module.
`@preline/overlay` is the auto-init entry: it scans the DOM and initializes matching elements automatically.
```js
import "@preline/overlay";
```
`@preline/overlay/non-auto` is the manual entry: use it when you want explicit control over when initialization happens, either via `autoInit()` or by creating a specific instance yourself.
```js
import HSOverlay from "@preline/overlay/non-auto";
HSOverlay.autoInit();
// Or initialize a specific element manually
const el = document.querySelector("#overlay");
if (el) new HSOverlay(el);
```
### TypeScript
This package ships with TypeScript type definitions. No additional `@types/` package is needed.
## Basic usage
The following example demonstrates the minimal HTML structure required for an overlay component. This is a base template without custom styling - you can apply your own CSS classes and styles as needed. The overlay appears when the button is clicked.
```html
<button type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="hs-unstyled-modal" data-hs-overlay="#hs-unstyled-modal">
Open modal
</button>
<div id="hs-unstyled-modal" class="hs-overlay hidden size-full fixed top-0 start-0 z-60 overflow-x-hidden overflow-y-auto pointer-events-none" role="dialog" tabindex="-1" aria-labelledby="hs-unstyled-modal-label">
<div class="hs-overlay-open:opacity-100 hs-overlay-open:duration-500 opacity-0 transition-all sm:max-w-lg sm:w-full m-3 sm:mx-auto">
<div class="pointer-events-auto">
<h3 id="hs-unstyled-modal-label">Title</h3>
Modal content
</div>
</div>
</div>
```
**Structure Requirements:**
- `data-hs-overlay`: Required on the trigger button, must be a valid CSS selector pointing to the overlay element
- `hs-overlay`: Required class on the overlay content container
- `hidden` class on the overlay initially
- Proper ARIA attributes (`aria-haspopup`, `aria-expanded`, `aria-controls`, `role="dialog"`, `aria-labelledby`)
**Initial State:**
- Set `aria-expanded="false"` on the trigger button initially
- Add `hidden` class to the overlay initially
- Overlay visibility is controlled by the plugin
## Configuration Options
### Data Attributes
Data attributes are added directly to HTML elements to configure overlay behavior.
| Attribute | Target Element | Type | Default | Description |
| --- | --- | --- | --- | --- |
| `data-hs-overlay-options` | Trigger button | object (JSON) | - | Defines the modal configuration. Should be added to the button (trigger). |
| `:hiddenClass` | Inside `data-hs-overlay-options` | string | `"hidden"` | Defines which classes will be added/removed when modal toggles. |
| `:emulateScrollbarSpace` | Inside `data-hs-overlay-options` | boolean | `false` | If `true`, then adds right padding to the body equal to the size of the scrollbar. Useful to prevent layout shift when overlay opens. |
| `:isClosePrev` | Inside `data-hs-overlay-options` | boolean | `true` | Determines whether the previous open modal will be closed when the next one is opened. |
| `:backdropClasses` | Inside `data-hs-overlay-options` | string | `"hs-overlay-backdrop transition duration fixed inset-0 bg-gray-900/50 dark:bg-neutral-900/80"` | Defines default backdrop classes. |
| `:backdropExtraClasses` | Inside `data-hs-overlay-options` | string | - | Allows to add additional classes besides those specified in `backdropClasses`. |
| `:backdropParent` | Inside `data-hs-overlay-options` | string | - | Allows to specify the selector of the element where the backdrop will be generated. |
| `:moveOverlayToBody` | Inside `data-hs-overlay-options` | number \| null | `null` | Moves the overlay to the body tag if the screen width is less than the value specified. |
| `:isToggleClassesImmediately` | Inside `data-hs-overlay-options` | boolean | `false` | When set to `true`, toggles `hidden`, `open`, and `opened` classes immediately instead of waiting for the open delay, resize debounce, or close transition. |
| `data-hs-overlay-backdrop-container` | Overlay content | string (CSS selector) | `null` | Backdrop element selector. Should be added to the overlay (content). |
| `data-hs-overlay-keyboard` | Overlay content | boolean | `true` | When set to `false`, the modal will not close when pressing the ESC button on the keyboard. Should be added to the overlay (content). |
### CSS Classes (Modifiers)
CSS class modifiers use Tailwind-style syntax with `--` prefix to control overlay behavior.
| Class Modifier | Target Element | Values | Default | Description |
| --- | --- | --- | --- | --- |
| `[--overlay-backdrop:*]` | Overlay content | `"static"` \| null | `null` | When backdrop is set to `"static"`, the modal will not close when clicking outside it. Should be added to the overlay (content). |
| `[--auto-hide:*]` | Overlay content | number | `0` | Milliseconds for auto-closing a modal. When set to `0`, the modal will not close automatically. Should be added to the overlay (content). |
| `[--auto-close:*]` | Overlay content | `'sm'` \| `'md'` \| `'lg'` \| `'xl'` \| `'2xl'` \| number | `null` | Screen resolution at which the overlay will close automatically. Should be added to the overlay (content). |
| `[--auto-close-equality-type:*]` | Overlay content | `'less-than'` \| null | `null` | This option determines the condition under which an overlay should automatically close based on the window's width. When set to `less-than`, the overlay will close if the window's width is less than or equal to the value defined in `[--auto-close:*]`. Otherwise the overlay will close if the window's width is greater than or equal to the `[--auto-close:*]` threshold. Should be added to the overlay (content). |
| `[--opened:*]` | Overlay content | `'sm'` \| `'md'` \| `'lg'` \| `'xl'` \| `'2xl'` \| number | `null` | Screen resolution at which the plugin will apply functionality for the initially opened overlay. Should be added to the overlay (content). |
| `[--body-scroll:*]` | Overlay content | boolean | `true` | When set to `false`, the body scroll will be hidden as soon as the modal opens. Should be added to the overlay (content). |
| `[--tab-accessibility-limited:*]` | Overlay content | `"true"` \| `"false"` | `"true"` | Restricts focus to elements within an overlay (or modal). Should be added to the overlay (content). |
| `[--has-autofocus:*]` | Overlay content | `"true"` \| `"false"` | `"true"` | Disables autofocus on the first focusable element when opening an overlay. Should be added to the overlay (content). |
| `[--has-dynamic-z-index:*]` | Overlay content | `"true"` \| `"false"` | `"false"` | When set to `"true"`, each newly opened element in the group will receive an incremented `z-index` inline, ensuring it appears above previously opened elements with the same base `z-index`. Should be added to the overlay (content). |
| `[--is-layout-affect:*]` | Overlay content | `"true"` \| `"false"` | `"false"` | Informs the plugin that the instance affects the website layout. Should be added to the overlay (content). |
| `[--toggle-classes-immediately:*]` | Overlay content | `"true"` \| `"false"` | `"false"` | When set to `"true"`, toggles `hidden`, `open`, and `opened` classes immediately instead of waiting for the open delay, resize debounce, or close transition. |
**Example:**
```html
<div class="hs-overlay --overlay-backdrop:static --auto-hide:5000">
<!-- Overlay content -->
</div>
```
### Required CSS Classes
These classes define the structure and must be present for the overlay to function.
| Class | Required On | Purpose |
| --- | --- | --- |
| `hs-overlay` | Overlay content container | Identifies the overlay content element |
### Optional CSS Classes
| Class | Required On | Purpose |
| --- | --- | --- |
| `hs-overlay-animation-target` | Element inside overlay | Defines an element inside the popup, after the end of whose animation the popup will be completely hidden. Has no parameters. |
| `autofocus` | Input element inside overlay | Focus the first input in a modal with the `autofocus` attribute on opening. Must be added to an input element inside a modal window. |
### Tailwind Modifiers
| Name | Description |
| --- | --- |
| `hs-overlay-open:*` | Defines classes that will be applied to any elements inside the `hs-overlay` when the overlay is open. |
| `hs-overlay-backdrop-open:*` | Defines classes that will be applied to backdrop when the overlay is open. |
| `hs-overlay-layout-open:*` | Defines classes that will be applied to any elements inside the body tag when the overlay is open. |
## JavaScript API
The `HSOverlay` object is available in the global `window` object after the plugin is loaded.
### Instance Methods
These methods are called on an overlay instance.
| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `open(cb)` | `cb`: `Function \| null` (optional) | `Promise<void>` | Forces the overlay to open programmatically. Optionally accepts a callback function that will be called after opening. Returns a Promise. |
| `close(forceClose, cb)` | `forceClose`: `boolean` (optional)<br>`cb`: `Function \| null` (optional) | `Promise<unknown>` | Forces the overlay to close programmatically. `forceClose` bypasses animations and closes immediately. Optionally accepts a callback function. Returns a Promise. |
| `minify(isMinified, cb)` | `isMinified`: `boolean`<br>`cb`: `Function \| null` (optional) | `void` | Minimizes or restores the overlay. Useful for drawer/sidebar components. |
| `updateToggles()` | None | `void` | Updates the toggle buttons. Useful after dynamically adding or removing toggle buttons. |
| `destroy()` | None | `void` | Destroys the overlay instance, removes all generated markup, classes, and event listeners. Use when removing overlay from DOM. |
### Static Methods
These methods are called directly on the `HSOverlay` class.
| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `HSOverlay.getInstance(target, isInstance)` | `target`: `HTMLElement \| string` (CSS selector)<br>`isInstance`: `boolean` (optional) | `HTMLElement \| { id: string \| number, element: HSOverlay } \| null` | Returns the overlay instance or element associated with the target. If `isInstance` is `true`, returns collection item object `{ id, element }` where `element` is the `HSOverlay` instance. If `isInstance` is `false` or omitted, returns the DOM element (`HTMLElement`). Returns `null` if overlay instance is not found. |
| `HSOverlay.open(target)` | `target`: `HSOverlay \| HTMLElement \| string` (CSS selector) | `void` | Opens the overlay identified by target. Accepts an overlay instance, DOM element, or CSS selector string. Static alternative to instance `open()` method. |
| `HSOverlay.close(target)` | `target`: `HSOverlay \| HTMLElement \| string` (CSS selector) | `void` | Closes the overlay identified by target. Accepts an overlay instance, DOM element, or CSS selector string. Static alternative to instance `close()` method. |
### Usage Examples
**Example 1: Using instance methods (public API)**
```javascript
// Create a new overlay instance
const modal = new HSOverlay(document.querySelector('#hs-modal'));
const openBtn = document.querySelector('#hs-open-btn');
// Open overlay programmatically
openBtn.addEventListener('click', () => {
modal.open();
});
```
**Example 2: Using static methods**
```javascript
const openBtn = document.querySelector('#hs-open-btn');
// Open overlay using static method (no instance needed)
openBtn.addEventListener('click', () => {
HSOverlay.open('#hs-modal');
});
```
**Example 3: Getting instance and using methods (recommended pattern)**
```javascript
// Get the overlay instance
const instance = HSOverlay.getInstance('#hs-modal', true);
if (instance) {
const { element } = instance; // element is the HSOverlay instance
const openBtn = document.querySelector('#hs-open-btn');
const closeBtn = document.querySelector('#hs-close-btn');
// Use instance methods
openBtn.addEventListener('click', () => {
element.open();
});
closeBtn.addEventListener('click', () => {
element.close();
});
// Clean up when removing from DOM
function removeOverlay() {
element.destroy();
}
}
```
**Example 4: Destroying overlay instance**
```javascript
const instance = HSOverlay.getInstance('#hs-modal', true);
if (instance) {
const { element } = instance;
const removeBtn = document.querySelector('#hs-remove-btn');
removeBtn.addEventListener('click', () => {
// Clean up before removing from DOM
element.destroy();
// Now safe to remove the element
document.querySelector('#hs-modal').remove();
});
}
```
## Events
Overlay instances emit events that can be listened to for lifecycle hooks and custom behavior.
| Event Name | When Fired | Callback Parameter | Description |
| --- | --- | --- | --- |
| `on:open` | After overlay is opened | `HSOverlay` (overlay instance) | Fires after the overlay has been displayed. Use for post-open actions like loading content or focusing elements. |
| `on:close` | After overlay is closed | `HSOverlay` (overlay instance) | Fires after the overlay has been hidden. Use for cleanup or state updates. |
### Event Usage Example
```javascript
// Get overlay instance
const instance = HSOverlay.getInstance('#hs-modal', true);
if (instance) {
const { element } = instance;
// Listen to open event
element.on('open', (overlayInstance) => {
console.log('Overlay opened:', overlayInstance);
// Perform actions after overlay opens
// e.g., load content, focus input, track analytics
});
// Listen to close event
element.on('close', (overlayInstance) => {
console.log('Overlay closed:', overlayInstance);
// Perform cleanup or state updates
});
}
```
## Common Patterns
### Pattern 1: Static Backdrop
Prevent closing when clicking outside the overlay.
```html
<div class="hs-overlay --overlay-backdrop:static">
<!-- Overlay content -->
</div>
```
### Pattern 2: Auto-close on Screen Size
Close overlay automatically at specific breakpoints.
```html
<div class="hs-overlay --auto-close:md --auto-close-equality-type:less-than">
<!-- Overlay content -->
</div>
```
### Pattern 3: Programmatic Control
Control overlay from external buttons.
```html
<button id="hs-open-modal">Open Modal</button>
<button id="hs-close-modal">Close Modal</button>
<div id="hs-modal-first" class="hs-overlay hidden">
<!-- Overlay content -->
</div>
<script>
const instance = HSOverlay.getInstance('#hs-modal-first', true);
if (instance) {
const { element } = instance;
document.querySelector('#hs-open-modal').addEventListener('click', () => {
element.open();
});
document.querySelector('#hs-close-modal').addEventListener('click', () => {
element.close();
});
}
</script>
```
## License
Copyright (c) 2026 Preline Labs.
Licensed under the [MIT License](https://opensource.org/licenses/MIT).