Apertura: Button-to-Dialog Morphing Modal Library in Vanilla JavaScript

Category: Javascript , Modal & Popup | August 31, 2026
Authorantuuanyf
Last UpdateAugust 31, 2026
LicenseMIT
Views0 views
Apertura: Button-to-Dialog Morphing Modal Library in Vanilla JavaScript

apertura is a vanilla JavaScript modal library that morphs the element that opens a dialog into the dialog itself.

A clicked button lifts from its original position, travels across the viewport, expands into the modal, then returns when the dialog closes.

Features

  • Trigger-to-dialog morph with reverse closing motion.
  • Spring animation for position and size changes.
  • Confirm, alert, prompt, and custom-content dialogs.
  • Center, anchor, in-place, and bottom placements.
  • Snappy, floaty, and cinematic motion presets.
  • Promise-based dialog results and async close checks.
  • Drag, overlay, and Escape-key dismissal controls.
  • Focus trapping, focus restoration, and scroll locking.
  • CSS custom properties for colors, spacing, typography, and sizing.

See It In Action

How To Use It

Installation

apertura is not published on npm yet. Current usage starts from a local clone. The repository uses ES modules and Vite for its demos.

If a Vite application imports src/index.js from a local copy, that entry loads the core stylesheet as part of the module. The package-style apertura and apertura/style.css imports belong to the project’s built package interface.

git clone https://github.com/antuuanyf/apertura.git
cd apertura
npm install
# Vanilla JavaScript demo
npm run demo
# Angular demo
npm run demo:angular

Basic Usage

Assume the repository is available under ./vendor/apertura/ in a Vite project. Pass the clicked element through origin to start the morph from that control.

modal.open() returns a Promise-like handle. The confirm action resolves true, cancel resolves false, and Escape, an overlay click, or drag dismissal resolves undefined. Leave out origin when no visible control caused the dialog to open.

import { modal } from './vendor/apertura/src/index.js';
document.querySelector('#remove-account').addEventListener('click', async (event) => {
  const confirmed = await modal.open({
    origin: event.currentTarget,
    title: 'Remove this account?',
    description: 'Saved preferences will be deleted.',
    confirmLabel: 'Remove',
    cancelLabel: 'Keep',
    variant: 'danger'
  });
  if (confirmed) {
    removeAccount();
  }
});

Advanced Usages

Confirm, Alert, and Prompt

modal.confirm() creates an OK/Cancel dialog. modal.alert() keeps the confirm action and removes Cancel. modal.prompt() adds a text field and resolves with its entered value.

const approved = await modal.confirm({
  origin: event.currentTarget,
  title: 'Publish this page?',
  variant: 'warning'
});
await modal.alert({
  origin: event.currentTarget,
  title: 'Changes saved'
});
const projectName = await modal.prompt({
  origin: event.currentTarget,
  title: 'Project name',
  placeholder: 'Dashboard redesign',
  defaultValue: 'New project'
});

Placement and Size

center is the default placement. anchor keeps the dialog close to its trigger, inplace expands over that location, and bottom uses a sheet layout. A missing origin sends anchor, inplace, and bottom back to centered placement.

The built-in width presets are 320px for sm, 440px for md, and 640px for lg, each constrained by the viewport. A number becomes a pixel width, and a CSS max-width string is also accepted.

modal.open({
  origin: event.currentTarget,
  title: 'Notification settings',
  placement: 'anchor',
  size: 'sm'
});
modal.open({
  origin: event.currentTarget,
  title: 'Responsive editor',
  placement: 'bottom',
  size: 'min(720px, calc(100vw - 40px))'
});

Custom Content

content accepts an HTML element or an HTML string. A render callback has direct access to the dialog body and receives { close, id }. Call close() with the value that should resolve the dialog Promise. The callback may return a cleanup function.

const result = await modal.open({
  origin: event.currentTarget,
  render(body, { close }) {
    const input = document.createElement('input');
    input.placeholder = 'Enter a label';
    const save = document.createElement('button');
    save.type = 'button';
    save.textContent = 'Save';
    save.addEventListener('click', () => close(input.value));
    body.append(input, save);
  }
});

Check Before Closing

beforeClose runs before the dialog leaves the screen. Return false to keep it open.

modal.open({
  origin: event.currentTarget,
  title: 'Save changes?',
  confirmLabel: 'Save',
  async beforeClose(result) {
    if (result !== true) {
      return;
    }
    const saved = await saveSettings();
    return saved ? true : false;
  }
});

Manage Open Dialogs

Every open() call returns a handle with an id. update() patches an active dialog, close() closes a specific dialog or the topmost dialog, and closeAll() closes the stack. destroy() resolves pending dialogs with undefined and removes the modal host.

const dialog = modal.open({
  title: 'Uploading file',
  description: 'Preparing upload...'
});
modal.update(dialog.id, {
  description: 'Upload is almost complete.'
});
modal.close(dialog.id, 'finished');
modal.close();
modal.closeAll();
modal.destroy();

Isolated Instances

createModal() creates another modal store and host. Set mountTo when the instance is created if its layer should live under a specific application root.

import { createModal } from './vendor/apertura/src/index.js';
const workspaceDialogs = createModal({
  mountTo: '#workspace'
});
workspaceDialogs.open({
  title: 'Workspace settings'
});

Dialog Options

  • origin (HTMLElement | null): Element used as the opening morph origin. Default is null.
  • originStyle (object | null): Overrides background, boxShadow, or borderRadius read from the origin.
  • closeOrigin (HTMLElement | null): Element used as the closing destination. Defaults to origin.
  • title (string): Heading for the default dialog card.
  • description (string): Body text for the default dialog card.
  • confirmLabel (string | null): Confirm button label. Default is OK. null hides the button.
  • cancelLabel (string | null): Cancel button label. Default is Cancel. null hides the button.
  • variant (string): Confirm-button variant. Built-in values are neutral, danger, success, and warning.
  • dismissible (boolean): Controls Escape, overlay click, and drag dismissal. Default is true.
  • gesture (boolean): Controls drag dismissal. Default is true.
  • labelledBy (string): Sets the ID used by aria-labelledby.
  • ariaLabel (string): Supplies a fallback accessible label when no aria-labelledby relationship is available.
  • content (HTMLElement | string | null): Replaces the default card body with a DOM node or HTML string.
  • render (function | null): Builds custom body content and receives { close, id }.
  • morph (string | object | null): Uses snappy, floaty, cinematic, or a custom Morph configuration.
  • size (string | number | null): Accepts sm, md, lg, a pixel number, or a CSS max-width value.
  • placement (string): Accepts center, anchor, inplace, or bottom.
  • beforeClose (function | null): Runs before closing and may keep the dialog open by returning false.
  • placeholder (string): Placeholder used by modal.prompt().
  • defaultValue (string): Initial value used by modal.prompt().

Host Configuration

modal.configure() updates animation, stacking, placement, and Morph values for the shared host. Set mountTo when calling createModal() because the mount location belongs to instance creation.

modal.configure({
  placementGap: 12,
  placementPadding: 20,
  morph: {
    stiffness: 160,
    damping: 16
  }
});
  • overlayDuration (number): Overlay fade duration. Default is 0.35.
  • enterDuration (number): Non-morph opening duration. Default is 0.5.
  • enterDistance (number): Opening vertical offset. Default is 16.
  • enterScale (number): Initial non-morph scale. Default is 0.94.
  • enterBlur (number): Initial non-morph blur. Default is 8.
  • exitDuration (number): Non-morph closing duration. Default is 0.28.
  • exitDistance (number): Closing vertical offset. Default is 10.
  • exitBlur (number): Closing blur. Default is 4.
  • underScale (number): Scale used for dialogs below the active dialog. Default is 0.96.
  • underY (number): Vertical offset used for dialogs below the active dialog. Default is 10.
  • underDuration (number): Stack transition duration. Default is 0.4.
  • underSpring (object): Stack spring. Defaults to stiffness 180, damping 22, and velocity 0.
  • placementGap (number): Gap used by positioned dialogs. Default is 8.
  • placementPadding (number): Viewport padding used by placement calculations. Default is 16.
  • morph (object): Global Morph configuration.
  • mountTo (string | HTMLElement): Mount point supplied when creating an isolated modal instance.

Morph Presets And Configuration

snappy, floaty, and cinematic change the main spring values. A custom object patches the current global Morph configuration for one dialog.

modal.open({
  origin: event.currentTarget,
  title: 'Fast response',
  morph: 'snappy'
});
modal.open({
  origin: event.currentTarget,
  title: 'Custom motion',
  morph: {
    damping: 10,
    velocity: 1800
  }
});
  • stiffness (number): Travel spring stiffness. Default is 144.
  • damping (number): Travel spring damping. Default is 14.
  • velocity (number): Initial travel velocity. Default is 2400.
  • sizeStiffness (number): Width and height spring stiffness. Default is 180.
  • sizeDamping (number): Width and height spring damping. Default is 22.
  • sizeDuration (number): Size transition duration value. Default is 0.32.
  • radiusDuration (number): Corner transition duration. Default is 0.32.
  • contentDuration (number): Content scale and blur duration. Default is 0.32.
  • colorDuration (number): Background transition duration. Default is 0.4.
  • colorDelay (number): Background transition delay. Default is 0.15.
  • shadowDuration (number): Shadow transition duration. Default is 0.6.
  • shadowDelay (number): Shadow transition delay. Default is 0.05.
  • contentScale (number): Initial content scale. Default is 2.
  • contentBlur (number): Initial content blur. Default is 8.
  • maxDuration (number): Opening Morph safety limit in milliseconds. Default is 1100.
  • closeDamping (number): Travel damping during close. Default is 20.
  • closeSizeDamping (number): Size damping during close. Default is 26.
  • closeVelocity (number): Initial closing velocity. Default is 1400.
  • closeContentDuration (number): Content hiding duration. Default is 0.16.
  • closeHandoffDuration (number): Final shell-to-origin handoff duration. Default is 0.18.
  • closeMaxDuration (number): Closing Morph safety limit in milliseconds. Default is 900.
  • closeRestDelta (number): Closing spring position threshold. Default is 0.8.
  • closeRestSpeed (number): Closing spring speed threshold. Default is 8.

API Methods

// Open a dialog and return a Promise-like handle.
const dialog = modal.open(options);
// Open the standard helper dialogs.
const confirmed = await modal.confirm(options);
await modal.alert(options);
const value = await modal.prompt(options);
// Close one dialog or the topmost dialog.
modal.close(dialog.id, 'saved');
modal.close();
// Close every active dialog.
modal.closeAll();
// Patch an active dialog.
modal.update(dialog.id, {
  description: 'Updated content'
});
// Change host configuration.
modal.configure({
  placementGap: 12
});
// Remove the host and resolve pending dialogs.
modal.destroy();

Styling And Customization

Define the variables in your own stylesheet to change the theme.

-root {
  --apr-bg: #18181b;
  --apr-fg: #fafafa;
  --apr-muted: #a1a1aa;
  --apr-radius: 22px;
  --apr-btn-bg: #fafafa;
  --apr-btn-fg: #18181b;
}

Layout And Typography Variables

  • --apr-bg
  • --apr-fg
  • --apr-muted
  • --apr-radius
  • --apr-sheet-radius
  • --apr-shadow
  • --apr-padding
  • --apr-max-width
  • --apr-gap
  • --apr-font-family
  • --apr-font-size
  • --apr-line-height
  • --apr-font-weight
  • --apr-title-size
  • --apr-title-weight
  • --apr-z-index

Overlay Variables

  • --apr-overlay
  • --apr-overlay-blur
  • --apr-inplace-overlay

Button And Variant Variables

  • --apr-btn-radius
  • --apr-btn-bg
  • --apr-btn-fg
  • --apr-danger-bg
  • --apr-danger-fg
  • --apr-success-bg
  • --apr-success-fg
  • --apr-warning-bg
  • --apr-warning-fg

Alternatives

You Might Be Interested In:


Leave a Reply