npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hcg-modal

v1.0.0

Published

Lightweight, dependency-free JavaScript popup modal in vanilla JS - sizes, positions, animations, focus trap, scroll lock, stacking, and bring-your-own-box support.

Readme

hcg-modal - JavaScript Popup Modal (Vanilla JS)

hcg-modal is a lightweight, dependency-free popup modal / dialog library written in plain JavaScript. It gives you a ready-made, accessible modal window - the kind you use for confirmations, alerts, forms, image lightboxes, cookie notices, or any "pop up over the page" content - without pulling in jQuery, a framework, or a build step.

Add one CSS file and one JS file, call hcgModal({ ... }), and you get a fully featured dialog: size and position control, custom width, scroll handling for long content, CSS-driven animations, an accessible focus trap, body scroll lock, layered stacking, footer buttons, promise-based results, an auto-close timer, and the option to use your own HTML as the dialog. It works on desktop and mobile (including back-button close), is roughly a few kilobytes, and is themeable through CSS custom properties. A thin React wrapper is included for React projects.

Live demo and documentation »

Features

  • Zero dependencies - plain JavaScript and CSS, no jQuery, no build step
  • Size variants: small, medium, large, fullscreen
  • Custom width - any pixel number or CSS length (600, '70%', '40rem'), capped to the viewport
  • Positions: center, top, bottom
  • Scroll modes - scrollBody: true pins the header/footer and scrolls only the body; false scrolls the whole dialog
  • Opt-in animations via className utility classes: hcg-modal--anim-fade, hcg-modal--anim-slide, hcg-modal--anim-pop (respect prefers-reduced-motion), or bring your own
  • Footer action buttons with primary / secondary / danger styles and onClick callbacks
  • Promise result - opened() resolves to the clicked button's value
  • beforeClose guard - veto or confirm a close (sync or async)
  • Auto-close timer with an optional progress bar that pauses on hover
  • Enter-to-confirm - confirmOnEnter triggers the primary button
  • Close on backdrop click, Escape key, close (X) button, and the browser/mobile back button - each toggleable
  • Layered stacking - open modals on top of each other; Esc and back close them top-down
  • Focus trap with focus restore, role="dialog" / aria-modal, and aria-label / aria-labelledby support
  • Bring your own box - use any element or HTML string as the entire dialog, with auto-wired .close elements
  • No layout shift - scroll lock compensates for the scrollbar width so the page never jumps
  • Themeable via CSS custom properties
  • React wrapper included (hcg-modal/react)

Install

Drop in the two files:

<link rel="stylesheet" href="hcg-modal.css">
<script src="hcg-modal.js"></script>

Or install from npm:

npm install hcg-modal

Usage

const modal = hcgModal({
  title: 'Delete item?',
  content: '<p>This action cannot be undone.</p>',
  size: 'small',
  buttons: [
    { text: 'Cancel', type: 'secondary', onClick: (m) => m.close() },
    { text: 'Delete', type: 'danger',    onClick: (m) => { /* ... */ m.close(); } }
  ]
});

modal.open();

Options

| Option | Default | Description | | --- | --- | --- | | title | '' | Header title (HTML or text). Omit to hide the header. | | content | '' | Body content: HTML string, text, or any DOM Node. A Node is moved into the modal (not copied) — clone it first if it must remain in the page. | | size | 'medium' | small, medium, large, or fullscreen. | | width | null | Custom dialog width, overriding the size preset. Number → px (600), or any CSS length string ('70%', '40rem'). Still capped to the viewport. | | position | 'center' | center, top, or bottom. | | scrollBody | true | How large content scrolls. true: dialog capped to the viewport, header and footer pinned, only the body scrolls. false: the whole dialog scrolls inside the overlay. | | closeOnBackdrop | true | Close when the backdrop is clicked. | | closeOnEsc | true | Close when the Escape key is pressed. | | closeOnBackButton | false | Close when the browser/mobile back button is pressed (uses the History API). Each stacked layer is closed by one back press. | | beforeClose | null | (reason) => boolean \| Promise<boolean> called before any close; return false to cancel. reason: button, backdrop, escape, close-button, back, timer, or api. | | timer | 0 | Auto-close after N milliseconds. 0 disables. | | timerProgressBar | false | Show a shrinking progress bar for the timer (pauses while hovered). | | confirmOnEnter | false | Pressing Enter triggers the primary footer button (ignored inside a textarea). | | showClose | true | Show the header close (X) button. | | ariaLabel | '' | Accessible name for the dialog (sets aria-label). Useful for a modal with no title. | | ariaLabelledBy | '' | Id of an element that labels the dialog (sets aria-labelledby). Takes precedence over ariaLabel. | | box | null | Use your own element or HTML string as the entire dialog (your own heading / content / footer). When set, title, content, buttons and showClose are ignored. A display:none/hidden element is revealed automatically. | | className | '' | Extra class(es) added to the overlay, for animation and theming. Built-in: hcg-modal--anim-fade, hcg-modal--anim-slide, hcg-modal--anim-pop, hcg-modal--glass. | | buttons | null | Array of { text, type, value, onClick(instance) }. type: primary, secondary, danger. value is what opened() resolves to when that button is clicked. | | onOpen | null | Callback fired after the modal opens. | | onClose | null | Callback fired when the modal closes. |

Security note: title and content accept HTML strings and are inserted as markup (content also accepts a DOM Node). Never pass unsanitized user input to them, or you risk XSS. Button labels (buttons[].text) are inserted as plain text and are safe. Sanitize any user-derived HTML before passing it in.

Back-button note: with closeOnBackButton, close() calls history.back() asynchronously. Opening and closing modals extremely fast (e.g. programmatic toggles in SPA route changes) can race the history pop/push. In normal use this is not an issue; avoid rapid back-to-back open/close on the same tick if you rely on the back-button integration.

Instance methods

| Method | Description | | --- | --- | | open() | Open the modal. Returns the instance (chainable). | | opened() | Open the modal and return a Promise that resolves when it closes, to the clicked button's value (or undefined when dismissed). | | close(reason) | Close the modal. reason is optional and passed to beforeClose. | | setContent(html\|node) | Replace the body content (HTML string or any DOM Node). A Node is moved into the modal — clone it first if it must stay in the page. | | setTitle(html) | Replace the header title (creates the header if it was omitted). | | isOpen() | Returns whether the modal is currently open. | | destroy() | Immediately remove the modal and detach listeners. Unlike close(), it does not run beforeClose, play the exit animation, or fire onClose. |

Promise result, beforeClose, timer, Enter

Await a modal with opened() and give buttons a value:

const ok = await hcgModal({
  title: 'Delete item?',
  buttons: [
    { text: 'Cancel', value: false },
    { text: 'Delete', type: 'danger', value: true }
  ]
}).opened();

if (ok) deleteItem();   // dismissing (Esc / backdrop / X) resolves to undefined

Guard closing with beforeClose (return false, or a Promise<boolean>, to cancel):

hcgModal({
  content: form,
  beforeClose: () => !hasUnsavedChanges() || window.confirm('Discard changes?')
}).open();

Auto-close as a toast, with Enter confirming the primary button:

hcgModal({ content: 'Saved', timer: 3000, timerProgressBar: true, showClose: false }).open();

hcgModal({ confirmOnEnter: true, buttons: [
  { text: 'Cancel', value: false },
  { text: 'OK', type: 'primary', value: true }
]}).opened();

Animations

The modal has no animation or duration options. Instead, pass an animation class through className and let CSS drive it. The modal reads the CSS transition time to know when to remove itself, so the duration is whatever your CSS sets.

hcgModal({ content: 'Hi', className: 'hcg-modal--anim-pop' }).open();

Built-in classes:

  • hcg-modal--anim-fade - plain opacity fade.
  • hcg-modal--anim-slide - slides in; direction follows position (top / center / bottom). With position: 'bottom' it slides up from the bottom edge like a sheet.
  • hcg-modal--anim-pop - combined rise, scale and blur for a richer entrance.

The dialog uses a spring easing curve (cubic-bezier(0.34, 1.56, 0.64, 1)) on open. Tune the speed with the --hcg-modal-duration variable, or write your own class entirely:

.my-anim .hcg-modal-dialog { transform: translateY(40px); opacity: 0; }
.my-anim.hcg-modal--visible .hcg-modal-dialog { transform: none; opacity: 1; }
hcgModal({ content: 'Hi', className: 'my-anim' }).open();

Bring your own box

Pass a ready-made element (or HTML string) as the whole dialog with the box option. The modal supplies only the backdrop, Esc / back-button close, scroll lock, focus trap and stacking - your box owns its layout and close buttons.

<div id="myBox" style="display:none">
  <div class="head">Heading</div>
  <div class="body">Content…</div>
  <div class="foot"><button class="x">Close</button></div>
</div>
var box = document.getElementById('myBox').cloneNode(true);  // clone to reuse
var modal = hcgModal({ box: box });
box.querySelector('.x').addEventListener('click', function () { modal.close(); });
modal.open();

The modal moves the node into itself and removes it on close, so clone the original if you want to reopen it. A display:none or hidden box is shown automatically.

Auto-wired close: any element inside the box (or content) with class .close or a [data-hcg-close] attribute is automatically wired to close the modal - no JS needed. Your own click handlers still work alongside it (close() is safe to call more than once):

<div id="myBox" style="display:none">
  <div class="body">Content…</div>
  <div class="foot">
    <button class="close">Cancel</button>      <!-- auto-wired -->
    <button class="save">Save</button>          <!-- wire yourself -->
  </div>
</div>

Theming

Override the CSS custom properties on .hcg-modal to restyle without editing the stylesheet:

.hcg-modal {
  --hcg-modal-bg: #1e1e1e;
  --hcg-modal-color: #eaeaea;
  --hcg-modal-primary: #8b5cf6;
  --hcg-modal-radius: 14px;
  --hcg-modal-z: 99999;
}

Other hooks include --hcg-modal-overlay, --hcg-modal-shadow, --hcg-modal-border, --hcg-modal-duration, and the per-type button colors.

Browser support

Modern evergreen browsers. Uses CSS custom properties and standard DOM APIs only.

License

MIT - HTML Code Generator (https://www.html-code-generator.com/)