Skip to content

About

Lightweight, dependency-free TypeScript dialogs with accessible modals, drawers, workflows, animations, theming, and draggable interactions.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Forge Dialog 0.7

A modern, lightweight, and highly customizable JavaScript dialog library for alerts, confirmations, modals, drawers, and interactive workflows.

Forge Dialog is a next-generation dialog component built for modern web applications. It provides beautiful animations, flexible layouts, accessibility, and a powerful API while remaining lightweight, dependency-free, and framework-agnostic.

Designed to replace traditional browser dialogs and heavy modal libraries, Forge Dialog makes it easy to build elegant user interactions — from simple alert boxes to complex multi-step workflows.

Highlights

  • 🚀 Lightweight and fast
  • 🎨 Fully customizable appearance
  • 📱 Responsive and mobile-friendly
  • ♿ Accessibility (ARIA) compliant
  • 🌙 Dark mode support
  • 🌐 Internationalization (i18n)
  • ✨ Smooth animations and transitions
  • 🔌 Plugin architecture
  • 📦 Zero dependency
  • 📜 TypeScript support

Install

npm install forgedialog
import { alert, confirm, prompt, open } from 'forgedialog';
import 'forgedialog/style.css';

Or via a plain <script> tag (UMD/IIFE global ForgeDialog):

<link rel="stylesheet" href="https://unpkg.com/forgedialog/dist/index.css" />
<script src="https://unpkg.com/forgedialog/dist/index.global.js"></script>
<script>
  ForgeDialog.alert('Hello!');
</script>

Quick start

import { alert, confirm, prompt, open } from 'forgedialog';

// Alert
await alert('Saved successfully.', { title: 'Success' });

// Confirm
const ok = await confirm('Delete this item?', { title: 'Please confirm' });

// Prompt
const name = await prompt('What is your name?', {
  defaultValue: 'Ada Lovelace',
  validate: (value) => (value.trim().length > 0 ? true : 'Name is required'),
});

// Fully custom dialog
const instance = open({
  title: 'Custom dialog',
  content: (container) => {
    const p = document.createElement('p');
    p.textContent = 'Anything can go here.';
    container.appendChild(p);
  },
  buttons: [{ text: 'Close', role: 'primary', autoFocus: true, onClick: (i) => i.close() }],
});
const result = await instance.whenClosed();

API

Function Returns Description
alert(message, options?) Promise<void> Single-button informational dialog.
confirm(message, options?) Promise<boolean> OK/Cancel dialog; resolves false on Escape/overlay close.
prompt(message, options?) Promise<string|null> Text-input dialog with optional validate().
open(options) DialogInstance Low-level API for fully custom dialogs.
setTheme('light'|'dark'|'system') void Overrides the OS color-scheme preference.
getTheme() ThemeMode Reads the current theme override.
setLabels(overrides) / getLabels() void / DialogLabels Overrides default labels (ok, cancel, close, notifications).
registerPlugin(plugin) void Registers a plugin ({ name, install?, hooks? }).
on(hookName, fn) / off(...) void Shorthand for a single lifecycle hook without a full plugin.

DialogInstance exposes open(), close(result?), whenClosed(), update(partialOptions), isOpen(), and the position methods getPosition(), setPosition(), and resetPosition().

Lifecycle hooks: beforeOpen, afterOpen, beforeClose, afterClose, beforeDestroy.

content strings are rendered as text. For untrusted markup, provide an explicit sanitizer:

open({ html: userContent, sanitizeHtml: (html) => DOMPurify.sanitize(html) });

For markup already guaranteed to be trusted, unsafeHtml remains available. Lifecycle failures are cleaned up automatically and may be observed with onError(error, instance).

Per-dialog appearance and dragging

Appearance overrides are scoped to one dialog and can be updated at runtime. Numeric opacity values are clamped to 0..1; numeric widths and blur values are interpreted as pixels.

const dialog = open({
  title: 'Movable inspector',
  message: 'Drag the header or focus it and use the arrow keys.',
  appearance: {
    opacity: 0.9,
    overlayOpacity: 0.55,
    backdropBlur: 12,
    surfaceColor: '#12141a',
    radius: { topLeft: 24, topRight: 24, bottomRight: 4, bottomLeft: 4 },
    titleColor: '#ffd166',
    titleBackground: '#1b1740',
    titleOpacity: 0.95,
    contentColor: '#c8ccd4',
    contentOpacity: 0.8,
    borderColor: '#7c5cff',
    borderOpacity: 0.6,
    borderWidth: 2,
    // A preset, any CSS box-shadow, or a shadow composed from its parts.
    shadow: { angle: 135, distance: 24, blur: 60, color: '#000', opacity: 0.45 },
    hover: {
      borderColor: '#9f7bff',
      titleColor: '#ffffff',
      shadow: 'xl',
      lift: 6,
      scale: 1.02,
      duration: 150,
    },
  },
  draggable: {
    axis: 'both',
    bounds: 'viewport',
    persistKey: 'inspector',
    keyboardStep: 8,
    onDragEnd: ({ position }) => console.log(position),
  },
});

dialog.setPosition({ x: 40, y: 24 });
dialog.update({ appearance: { opacity: 1, shadow: 'md' } });

draggable: true remains supported. A selector or element can be supplied as handle; movement can be constrained to x, y, the viewport, an element, or a DOMRect. Bottom sheets keep their dedicated swipe-to-dismiss gesture and ignore general dragging.

Colors and opacity are scoped per component, so the surface, title, body, and border can each be tuned on their own. titleBackground paints the header, which is transparent by default and is clipped by the dialog's corners. radius takes one value for every corner, any CSS border-radius value, or a per-corner object; corners left out of that object keep the theme radius. Drawers stay square and bottom sheets keep rounding only their top corners unless radius says otherwise.

shadow accepts a preset name, a raw CSS box-shadow, or the parts of one: angle picks the direction the shadow falls (0 up, 90 right, 180 down, 270 left), while distance, blur, spread, color, opacity, and inset control its shape and strength.

hover restyles the dialog only while the pointer is over it, and only for dialogs that ask for it, so every other dialog stays completely static. It reuses the same color and shadow options and adds lift, scale, and duration. Hover transforms honour prefers-reduced-motion and are suspended while a dialog is being dragged so they cannot push it outside its bounds.

Both forgedialog and forgedialog/core ship the full appearance applier, so none of this needs an extra import. The single-purpose entries (forgedialog/alert, /confirm, /prompt) stay on a lightweight applier that covers surface opacity, backdrop, border, and shadow presets, and ignore the richer options; import forgedialog/appearance alongside one of them to upgrade it:

import 'forgedialog/appearance';
import { alert } from 'forgedialog/alert';

Advanced UI and workflows

Forge Dialog uses the native <dialog> top layer and adds typed outcomes, close reasons, AbortSignal, drawers, bottom sheets, lightboxes, loading states, toast notifications, command palettes, and persisted branching wizards.

import { drawer, toast, wizard } from 'forgedialog';

toast('Saved', { tone: 'success' });
// A duration of 0 or Infinity keeps the toast until dismiss() is called.
const upload = toast('Uploading…', { duration: 0 });
upload.dismiss();

drawer({ title: 'Settings', side: 'right', content: renderSettings });

const flow = wizard({
  initialData: { email: '' },
  steps: [
    { id: 'account', title: 'Account', render: renderAccount, validate: validateAccount },
    { id: 'review', title: 'Review', render: renderReview },
  ],
});
const data = await flow.result;

Tree-shakable entry points are available at forgedialog/core, forgedialog/interactions, forgedialog/animations, forgedialog/appearance, forgedialog/presentation, and forgedialog/workflows. forgedialog/interactions exposes the standalone draggable controller without pulling in dialog APIs. Framework integrations are exported from forgedialog/react, forgedialog/vue, forgedialog/svelte, and forgedialog/web-component.

Single-purpose entry points minimize simple dialogs further:

import { alert } from 'forgedialog/alert';
import { confirm } from 'forgedialog/confirm';
import { prompt } from 'forgedialog/prompt';

The main forgedialog entry automatically enables advanced dragging and animations. A core-only application can opt into either capability without importing presentation or workflow APIs:

import 'forgedialog/interactions';
import 'forgedialog/animations';
import { open } from 'forgedialog/core';

For the smallest stylesheet, compose only the layers a page uses:

import 'forgedialog/style/core.css';
import 'forgedialog/style/forms.css'; // form fields and file dropzones
import 'forgedialog/style/workflows.css'; // wizard stepper
import 'forgedialog/style/toast.css';
import 'forgedialog/style/lightbox.css';
import 'forgedialog/style/command.css';
import 'forgedialog/style/draggable.css';

forgedialog/style.css remains the all-in-one compatibility stylesheet.

Development

npm install
npm run build       # emit dist/ (ESM, CJS, IIFE, .d.ts, CSS)
npm run test         # vitest + jsdom
npm run test:coverage # unit tests with enforced coverage thresholds
npm run test:e2e      # Chromium, Firefox, WebKit, and mobile browser checks
npm run typecheck
npm run lint
npm run size          # enforce runtime and CSS gzip budgets
npm run analyze       # report gzip size and largest source modules
npm run test:treeshake # verify an alert-only consumer excludes optional features
npm run check:package # validate ESM/CJS exports and declarations
npm run api:check     # reject unreviewed public API changes
npm run api:update    # intentionally refresh the reviewed API report
npm run test:consumers # build Vite, Webpack, and Node CJS fixtures
npm run validate      # run every non-browser quality and packaging gate
npm run validate:all  # run validate plus the cross-browser suite
npm run demo         # build and serve the demo/ page
npm run site         # build and serve the marketing site in site/
npm run site:icons   # re-render the site's PNG icons from site/icon.svg

Releases use Changesets (npm run changeset) and the protected GitHub release workflow. The workflow generates a CycloneDX SBOM and publishes with npm provenance through trusted publishing; local development and pull requests never publish packages. The process, and the one-time npm setup it depends on, are in docs/releasing.md.

Supported baselines are Chrome/Edge 88+, Firefox 78+, Safari 15.4+, and Node.js 20+ for package tooling and SSR imports.

Website

site/ holds a dependency-free landing page with an interactive appearance playground, a live demo page that runs every surface the library ships, and a full API reference — each published in English and Vietnamese. Build and serve them with npm run site. The icon, the generated sitemap.xml / robots.txt / llms.txt, the structured data, and the Cloudflare deployment settings are covered in docs/website.md.

Contributing

Setup, the checks CI runs, and the gotchas worth knowing are in CONTRIBUTING.md. Participation is governed by our Code of Conduct.

Found a security issue? Please report it privately — see SECURITY.md.

License

MIT

About

Lightweight, dependency-free TypeScript dialogs with accessible modals, drawers, workflows, animations, theming, and draggable interactions.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages