Modal

Modal displays content in a layer above the current page, pausing interaction until the user responds. FrontAlign modals are promise-based — every modal type returns a Promise that resolves when the dialog closes, making it trivial to chain actions, await confirmations, or sequence multiple dialogs.

Three modal types are supported: default for information and alert dialogs, confirm for action-verification dialogs that resolve true or false, and custom for attaching the FrontAlign lifecycle to a modal element already present in your DOM.

Getting Started

Initialize the modal system once on the client, then call the modal API from buttons, handlers, or React demo components. For documentation pages, keep the modal markup out of MDX and render real demos with dedicated client components.

import { Modal } from 'frontalign';
import 'frontalign/css';

document.querySelector('#open-modal').addEventListener('click', () => {
  Modal.alert({
    heading: 'Hello',
    content: 'Modal is working.',
    dismissText: 'Got it'
  });
});

Structure

The modal DOM is generated automatically by the runtime for default and confirm types. For custom type modals, author the markup manually using the same element and class names shown below.

ElementClass / AttributePurpose
RootmodalBackdrop overlay and positioning context. Required on all modals.
Alignment modifieris-center / is-top / is-bottomControls vertical position of the dialog on screen.
Open stateopenedApplied by the runtime when the modal becomes visible. Drives the CSS enter animation.
Dialogmodal-dialogWhite dialog panel. Contains header, body, and actions.
Headermodal-headerTitle text area at the top of the dialog.
Bodymodal-bodyMain content area. Accepts any HTML.
Actionsmodal-actionsButton row at the bottom of the dialog.
Close triggermodal-closeAny element with this class inside a custom modal triggers the close sequence.
Confirm triggermodal-confirmResolves the promise with true and closes the modal.

Default Modal

A standard information dialog. Resolves when the user dismisses it. Use it for success confirmations, error notices, informational announcements, or any situation where a single acknowledge action is sufficient.


With Icon (Default Modal)

Attach a status icon by passing the icon option. The runtime renders the animated icon above the heading automatically. Use success for positive results, error for failures, warning for caution states, and info for neutral announcements.

// Success — after a completed action
Modal.alert({
  heading: 'Payment Successful',
  content: 'Your payment of $49.00 has been processed.',
  icon: { visible: true, type: 'success' }
});

// Error — when something goes wrong
Modal.alert({
  heading: 'Upload Failed',
  content: 'The file could not be uploaded. Please try again.',
  icon: { visible: true, type: 'error' }
});

// Warning — before a risky action
Modal.alert({
  heading: 'Storage Almost Full',
  content: 'You have used 95% of your storage quota.',
  icon: { visible: true, type: 'warning' }
});

// Info — neutral announcement
Modal.alert({
  heading: 'Scheduled Maintenance',
  content: 'The service will be unavailable on Sunday from 2:00–4:00 AM.',
  icon: { visible: true, type: 'info' }
});
Icon typeCSS class appliedUse case
successis-success is-animatedPositive confirmation — saved, submitted, completed.
erroris-error is-animatedFailure state — request failed, validation error.
warningis-warning is-animatedCaution — irreversible action, expiry notice.
infois-info is-animatedNeutral information — tips, announcements, maintenance.

Confirm Modal

Returns a Promise that resolves true when the user confirms, and false when the user cancels, presses ESC, or clicks the backdrop. Use it before any destructive or irreversible action.

document.querySelector('#delete-btn').addEventListener('click', async () => {
  const confirmed = await Modal.confirm({
    heading: 'Delete Account?',
    content: 'This action is permanent and cannot be undone.',
    actions: {
      cancelText: 'Cancel',
      confirmText: 'Delete'
    }
  });

  if (confirmed) {
    await deleteAccount();
  }
});

With Icon (Confirm Modal)

document.querySelector('#remove-btn').addEventListener('click', async () => {
  const confirmed = await Modal.confirm({
    heading: 'Remove Member?',
    content: 'This will revoke their access immediately.',
    icon: { visible: true, type: 'warning' },
    actions: {
      cancelText: 'Keep',
      confirmText: 'Remove'
    }
  });

  if (confirmed) {
    await removeMember();
  }
});

Custom Modal

Attach FrontAlign's lifecycle and animation system to a modal element already present in your DOM. The id option accepts any valid CSS selector. Any element with modal-close inside the dialog triggers the close sequence without additional JavaScript.

// Trigger the custom modal from anywhere
document.querySelector('#open-email-modal').addEventListener('click', () => {
  Modal.custom({
    id: '#email-modal',
    onOpened: (el) => {
      el.querySelector('#email-input').focus();
    },
    onClosed: (el, reason) => {
      console.log('Modal closed, reason:', reason);
    }
  });
});

A custom modal type is a fully customizable type of modal. You must use an HTML template like the one below.

<div
  id="custom-modal-demo"
  class="modal is-center"
  role="dialog"
  aria-modal="true"
  aria-labelledby="custom-modal-demo-title"
  aria-describedby="custom-modal-demo-desc"
>
  <div class="modal-dialog" tabindex="-1">
    <div class="modal-header">
      <h2 id="custom-modal-demo-title">Delete your account?</h2>
      <button
        class="modal-close-icon"
        aria-label="Close"
        type="button"
      >
        ✕
      </button>
    </div>

    <div class="modal-body" id="custom-modal-demo-desc">
      This action cannot be undone. Your account and all associated
      data (profile, history, saved files) will be permanently deleted.
    </div>

    <div class="modal-actions">
      <button class="button is-secondary modal-close" type="button">
        Cancel
      </button>
      <button class="button is-primary-ghost" type="button">
        Yes, delete
      </button>
    </div>
  </div>
</div>

Guard Mode

When guardMode is true, the user cannot close the modal via ESC or backdrop click. A single required-action button is rendered instead of the standard dismiss button, forcing the user to take a deliberate action before continuing. Use it for session expiry, mandatory terms acceptance, or critical system notices.

// Trigger when session token expires
onSessionExpired(() => {
  Modal.alert({
    heading: 'Session Expired',
    content: 'Your session has expired. Please log in again to continue.',
    guardMode: true,
    guardButtonText: 'Log in again',
    guardButtonClass: 'is-primary-ghost',
    guardButtonUrl: '/login'
});
});

Alignment

Control the vertical position of the dialog on screen using the align option.

ValueCSS class appliedDescription
centeris-centerCentered vertically on screen. Default value.
topis-topDialog appears from the top of the viewport.
bottomis-bottomDialog slides up from the bottom of the viewport.
// Top-aligned — notification style
Modal.alert({
  heading: 'New Message',
  content: 'You have a new message from Alex.',
  align: 'top',
  dismissText: 'View'
});

// Bottom-aligned — mobile sheet style
Modal.alert({
  heading: 'Share Location?',
  content: 'Allow this app to access your location while in use.',
  align: 'bottom',
  actions: { cancelText: 'Deny', confirmText: 'Allow' }
});

Queue

Pass true as the third argument to add the modal to the static queue. Queued modals open one after another — each one waits until the current modal is fully closed before the next begins. Queue order is first-in first-out.

// Onboarding flow — three sequential dialogs
Modal.alert({
  heading: 'Welcome to FrontAlign',
  content: 'Let\'s take a quick tour of the features.',
  dismissText: 'Next'
}, true);

faApp.modal({
  heading: 'Components',
  content: 'Build UIs with modal, badge, accordion, toast, and more.',
  dismissText: 'Next'
}, true);

Modal.alert({
  heading: 'Enable Notifications?',
  content: 'Stay updated with release notes and new components.',
  actions: { cancelText: 'Skip', confirmText: 'Enable' }
}, true);

Lifecycle Callbacks

All four callbacks receive the modal DOM element as their first argument. onClose and onClosed also receive a reason string identifying what triggered the close. Returning false from onOpen or onClose cancels the operation and keeps the modal in place.

CallbackArgumentsCancellableDescription
onOpen(el)Fires before the opening animation. Return false to cancel.
onOpened(el)Fires after the opening animation completes.
onClose(el, reason)Fires before the closing animation. Return false to cancel.
onClosed(el, reason)Fires after the modal is fully removed from the DOM.

Close reasons: dismiss · confirm · esc · backdrop · guard-exit

faApp.modal('confirm', {
  heading: 'Archive Project?',
  content: 'This will move the project to the archive.',
  actions: { cancelText: 'Cancel', confirmText: 'Archive' },

  onOpen: (el) => {
    // return false to prevent opening — e.g. if a condition is not met
    if (!userHasPermission('archive')) return false;
    console.log('Modal opening', el);
  },

  onOpened: (el) => {
    // Focus the first interactive element manually
    el.querySelector('button')?.focus();
  },

  onClose: (el, reason) => {
    // Block closing via ESC — force an explicit choice
    if (reason === 'esc') return false;
  },

  onClosed: (el, reason) => {
    if (reason === 'confirm') {
      archiveProject();
    }
  }
});

Promise API

All modal types return a Promise. Use async/await or .then() to react to the outcome.

TypeResolves withRejects
alertundefined on dismiss
confirmtrue on confirm, false on cancel / esc / backdrop
customundefined immediately after openIf element not found

async / await

Chain multiple modals — await each one before proceeding to the next.

document.querySelector('#delete-btn').addEventListener('click', async () => {
  // Step 1 — confirm intent
  const confirmed = await Modal.confirm({
    heading: 'Delete Item?',
    content: 'This action cannot be undone.',
    actions: { cancelText: 'Cancel', confirmText: 'Delete' }
  });

  if (!confirmed) return;

  // Step 2 — perform delete
  await deleteItem();

  // Step 3 — show success
  await Modal.alert({
    heading: 'Item Deleted',
    content: 'The item has been permanently removed.',
    icon: { visible: true, type: 'success' }
  });

  location.reload();
});

.then()

Modal.confirm({
  heading: 'Publish Article?',
  content: 'This will make your article publicly visible.',
  actions: { cancelText: 'Cancel', confirmText: 'Publish' }
}).then((confirmed) => {
  if (confirmed) publishArticle();
});

Continue with the React Integration guide for provider setup, hooks, and Next.js usage.

Accessibility

FrontAlign modals follow ARIA dialog best practices by default. The focusFirst option auto-focuses the dialog when it opens, ensuring keyboard and screen reader users can interact immediately without manual focus management.

// focusFirst is true by default
faApp.modal('default', {
  heading: 'Terms Updated',
  content: 'Please review our updated privacy policy.',
  focusFirst: true // default — no need to set explicitly
});

// Disable when you manage focus manually via onOpened
faApp.modal('custom', {
  id: '#signup-modal',
  focusFirst: false,
  onOpened: (el) => el.querySelector('#first-name-input')?.focus()
});

Configuration Reference

Default & Confirm Options

OptionTypeDefaultDescription
headingstringundefinedTitle text at the top of the modal.
contentstring"This is a Modal message"Body message. Accepts plain text.
alignstring"center"Vertical position. Accepts top, center, bottom.
disposebooleanfalseClears all object references from memory after closing.
dismissTextstring"OK"Close button label. Applies to default type only.
iconobject{ visible: false, type: "none" }Icon config. Types: success, error, warning, info.
actionsobject{ cancelText: "No", confirmText: "Yes" }Button labels. Applies to confirm type only.
guardModebooleanfalsePrevents closing via ESC or backdrop click.
guardButtonTextstring"Go here"Label for the guard action button.
guardButtonClassstring""Additional CSS classes for the guard button.
guardButtonUrlstring""Redirect URL triggered on guard button click.
closeOnEscbooleantrueAllow closing with the Escape key.
backdropbooleantrueShow the background overlay behind the dialog.
backdropClosebooleantrueAllow closing by clicking the backdrop.
focusFirstbooleantrueAuto-focus the dialog on open for accessibility.
onOpenfunctionnullCallback before opening animation. Return false to cancel.
onOpenedfunctionnullCallback after opening animation completes.
onClosefunctionnullCallback before closing animation. Return false to cancel.
onClosedfunctionnullCallback after modal is removed from the DOM.

Custom Options

OptionTypeDefaultDescription
idstring"#"CSS selector for the existing modal element in the DOM.
onOpenfunctionnullCallback before opening animation. Return false to cancel.
onOpenedfunctionnullCallback after opening animation completes.
onClosefunctionnullCallback before closing animation. Return false to cancel.
onClosedfunctionnullCallback after closing animation completes.

Notes

  • faApp.modal() always returns a Promise — use await to chain actions after dismissal.
  • The default type resolves undefined; the confirm type resolves true or false.
  • guardMode: true overrides both closeOnEsc and backdropClose — set them explicitly to false as well for clarity.
  • Returning false from onOpen or onClose cancels the animation and keeps the modal in its current state.
  • Queue order is first-in first-out — modals open in the exact order they are registered.
  • The custom type rejects its promise if the selector passed to id matches no element.
  • For custom modals, any element with modal-close inside the dialog triggers the close sequence without additional JavaScript.
  • dispose: true is recommended for one-off informational dialogs to prevent memory accumulation in long-running SPAs.
  • Use the React Integration guide when building with React or Next.js.

FrontAlign