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.
| Element | Class / Attribute | Purpose |
|---|---|---|
| Root | modal | Backdrop overlay and positioning context. Required on all modals. |
| Alignment modifier | is-center / is-top / is-bottom | Controls vertical position of the dialog on screen. |
| Open state | opened | Applied by the runtime when the modal becomes visible. Drives the CSS enter animation. |
| Dialog | modal-dialog | White dialog panel. Contains header, body, and actions. |
| Header | modal-header | Title text area at the top of the dialog. |
| Body | modal-body | Main content area. Accepts any HTML. |
| Actions | modal-actions | Button row at the bottom of the dialog. |
| Close trigger | modal-close | Any element with this class inside a custom modal triggers the close sequence. |
| Confirm trigger | modal-confirm | Resolves 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 type | CSS class applied | Use case |
|---|---|---|
success | is-success is-animated | Positive confirmation — saved, submitted, completed. |
error | is-error is-animated | Failure state — request failed, validation error. |
warning | is-warning is-animated | Caution — irreversible action, expiry notice. |
info | is-info is-animated | Neutral 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.
| Value | CSS class applied | Description |
|---|---|---|
center | is-center | Centered vertically on screen. Default value. |
top | is-top | Dialog appears from the top of the viewport. |
bottom | is-bottom | Dialog 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.
| Callback | Arguments | Cancellable | Description |
|---|---|---|---|
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.
| Type | Resolves with | Rejects |
|---|---|---|
alert | undefined on dismiss | — |
confirm | true on confirm, false on cancel / esc / backdrop | — |
custom | undefined immediately after open | If 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
| Option | Type | Default | Description |
|---|---|---|---|
heading | string | undefined | Title text at the top of the modal. |
content | string | "This is a Modal message" | Body message. Accepts plain text. |
align | string | "center" | Vertical position. Accepts top, center, bottom. |
dispose | boolean | false | Clears all object references from memory after closing. |
dismissText | string | "OK" | Close button label. Applies to default type only. |
icon | object | { visible: false, type: "none" } | Icon config. Types: success, error, warning, info. |
actions | object | { cancelText: "No", confirmText: "Yes" } | Button labels. Applies to confirm type only. |
guardMode | boolean | false | Prevents closing via ESC or backdrop click. |
guardButtonText | string | "Go here" | Label for the guard action button. |
guardButtonClass | string | "" | Additional CSS classes for the guard button. |
guardButtonUrl | string | "" | Redirect URL triggered on guard button click. |
closeOnEsc | boolean | true | Allow closing with the Escape key. |
backdrop | boolean | true | Show the background overlay behind the dialog. |
backdropClose | boolean | true | Allow closing by clicking the backdrop. |
focusFirst | boolean | true | Auto-focus the dialog on open for accessibility. |
onOpen | function | null | Callback before opening animation. Return false to cancel. |
onOpened | function | null | Callback after opening animation completes. |
onClose | function | null | Callback before closing animation. Return false to cancel. |
onClosed | function | null | Callback after modal is removed from the DOM. |
Custom Options
| Option | Type | Default | Description |
|---|---|---|---|
id | string | "#" | CSS selector for the existing modal element in the DOM. |
onOpen | function | null | Callback before opening animation. Return false to cancel. |
onOpened | function | null | Callback after opening animation completes. |
onClose | function | null | Callback before closing animation. Return false to cancel. |
onClosed | function | null | Callback after closing animation completes. |
Notes
faApp.modal()always returns aPromise— useawaitto chain actions after dismissal.- The
defaulttype resolvesundefined; theconfirmtype resolvestrueorfalse. guardMode: trueoverrides bothcloseOnEscandbackdropClose— set them explicitly tofalseas well for clarity.- Returning
falsefromonOpenoronClosecancels 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
customtype rejects its promise if the selector passed toidmatches no element. - For
custommodals, any element withmodal-closeinside the dialog triggers the close sequence without additional JavaScript. dispose: trueis 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.