Skip to content

html-code-generator/hcg-modal

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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/)

About

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

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages