# Overlay

The Overlay Tailwind plugin is an unstyled JavaScript utility for creating modals, drawers, and more, enhancing user interaction and content layout.

[![npm](https://img.shields.io/badge/npm-v4.2.0-blue)](https://www.npmjs.com/package/@preline/overlay) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Demo](https://img.shields.io/badge/demo-online-brightgreen)](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).
