Now.js Framework Documentation
Modal - Modal Dialog System
Modal - Modal Dialog System
Complete documentation for the Modal class - a feature-rich component for creating and managing modal dialogs with animation, accessibility, and data binding support.
📋 Table of Contents
- Overview
- Installation
- Basic Usage
- Configuration Options
- Data Binding
- Lifecycle Callbacks
- Accessibility
- JavaScript API
- Examples
- API Reference
- Best Practices
Overview
Modal is a class for creating feature-rich modal dialogs with overlay display, backdrop, animation, keyboard navigation, and data binding capabilities.
Key Features
- ✅ Programmatic Creation: Create modals with JavaScript instantly
- ✅ Animation Support: Built-in fade-in/fade-out animations
- ✅ Backdrop Management: Automatic backdrop via
BackdropManager - ✅ Keyboard Support: Close with ESC key
- ✅ Focus Management: Automatic focus and focus trap
- ✅ Accessibility: ARIA attributes and
inertproperty support - ✅ Data Binding: Bind data to modal elements
- ✅ Content Sanitization: XSS protection with automatic sanitization
- ✅ Lifecycle Callbacks: Hooks for each lifecycle stage
- ✅ Element Cleanup: Automatic cleanup when closing
When to Use Modal
✅ Use Modal when:
- Creating modals programmatically
- Need animation and full accessibility
- Need to bind data to modal content
- Want JavaScript API control
❌ Don't use Modal when:
- Modal elements already exist in HTML (use
ModalDataBinderinstead) - Need simple modals without special features
- Don't want backdrop
Installation
Modal is shipped in the Now.js core bundle. Load the core products first, then use the class through window.Modal.
<link rel="stylesheet" href="Now/dist/now.core.min.css">
<script src="Now/dist/now.core.min.js"></script>// No import needed after now.core loads
console.log(window.Modal); // Modal class
console.log(window.Now?.getManager?.('modal')); // Registered manager when the registry is availableImplementation Files
Now/js/Modal.jsNow/css/modal.css
Dependencies
- BackdropManager – Required for
show()andhide() - FormManager – Optional cleanup for forms rendered inside the modal body
- ElementManager – Optional cleanup and re-scan for enhanced elements inside the modal body
- DOMPurify – Optional HTML sanitization helper used when available
Basic Usage
1. Simple Modal
// Create modal instance
const modal = new Modal({
title: 'Welcome',
content: '<p>This is your first modal!</p>'
});
// Show modal
modal.show();2. Modal with Options
const modal = new Modal({
id: 'my-modal',
title: 'User Information',
content: `
<div class="user-info">
<img data-modal-target="avatar" class="avatar">
<h3 data-modal-target="name"></h3>
<p data-modal-target="email"></p>
</div>
`,
className: 'modal-user-card',
closeButton: true,
backdrop: true,
keyboard: true,
animation: true
});
modal.show();.modal.modal-user-card .modal-dialog {
max-width: 500px;
}3. Close Modal
// Close modal
modal.hide();
// Or press ESC key
// Or click backdrop (if enabled)Configuration Options
Modal supports these options:
Basic Options
| Option | Type | Default | Description |
|---|---|---|---|
id |
string | auto-generated | Modal element ID |
title |
string | '' |
Modal title |
content |
string|Node|DocumentFragment | '' |
Modal content (HTML string, DOM node, or document fragment) |
className |
string | '' |
Additional CSS classes on the root .modal element. Use this for sizing or styling variants. |
titleClass |
string | '' |
Additional CSS classes on .modal-title |
Display Options
| Option | Type | Default | Description |
|---|---|---|---|
closeButton |
boolean | true |
Show close button |
animation |
boolean | true |
Enable animations |
backdrop |
boolean | true |
Close on backdrop click |
keyboard |
boolean | true |
Close on ESC key |
focus |
boolean | true |
Auto focus management |
Backdrop Options
| Option | Type | Default | Description |
|---|---|---|---|
backdropOpacity |
number | 0.5 |
Backdrop opacity |
backdropColor |
string | 'rgba(0,0,0,0.5)' |
Backdrop color |
Note: The current implementation always creates a backdrop through BackdropManager. backdrop: false disables click-to-close behavior only; it does not remove the backdrop layer.
Sizing And Styling
Modal sizing is CSS-driven. Add a className to the root .modal element and target .modal-dialog in CSS.
.modal.modal-compact .modal-dialog {
max-width: 420px;
}
.modal.modal-tall .modal-dialog {
max-height: 95vh;
}By default, .modal-dialog uses max-width: 600px and max-height: 90vh. On screens 640px wide or smaller, modal.css switches the dialog to a full-screen layout.
Lifecycle Callbacks
| Option | Type | Description |
|---|---|---|
onShow |
function | Called before modal shows |
onShown |
function | Called after modal shown |
onHide |
function | Called before modal hides |
onHidden |
function | Called after modal hidden |
Example of using Options
const modal = new Modal({
id: 'confirm-modal',
title: 'Confirm deletion',
content: '<p>Are you sure to delete this item??</p>',
className: 'modal-confirm modal-danger',
closeButton: true,
backdrop: true,
keyboard: true,
animation: true,
backdropOpacity: 0.7,
onShow: function() {
console.log('Modal Showing...');
},
onShown: function() {
console.log('Modal Show finished');
},
onHide: function() {
console.log('Modal Closing...');
},
onHidden: function() {
console.log('Modal Finished closing');
}
});Data Binding
Modal supports data binding to elements via data-modal-target attribute:
Basic Data Binding
// Create modal with data binding targets
const modal = new Modal({
title: 'User Profile',
content: `
<div class="user-profile">
<img data-modal-target="avatar" class="avatar">
<h3 data-modal-target="name"></h3>
<p data-modal-target="email"></p>
<p data-modal-target="bio"></p>
</div>
`
});
// Bind data
modal.bindData({
avatar: '/images/user.jpg',
name: 'John Doe',
email: 'john@example.com',
bio: 'Software Developer'
});
// Show modal
modal.show();Update Data
// Update partial data
modal.updateData({
bio: 'Senior Software Developer'
});
// Or use method chaining
modal.updateData({
name: 'John Doe (Admin)'
}).show();Get Bound Data
// Get all bound data
const data = modal.getData();
console.log(data);
// { avatar: '/images/user.jpg', name: 'John Doe', ... }Element Type Binding
Modal automatically binds data based on element type:
const modal = new Modal({
content: `
<!-- Image: sets src -->
<img data-modal-target="productImage">
<!-- Link: sets href -->
<a data-modal-target="productLink">View Product</a>
<!-- Input: sets value -->
<input data-modal-target="productName">
<!-- Text content (default) -->
<h3 data-modal-target="title"></h3>
<p data-modal-target="description"></p>
<!-- HTML content (use data-modal-html) -->
<div data-modal-target="richContent" data-modal-html></div>
`
});
modal.bindData({
productImage: '/images/product.jpg',
productLink: '/products/123',
productName: 'Sample Product',
title: 'New Product',
description: 'Product details',
richContent: '<strong>HTML</strong> content'
});Content Sanitization
Modal will automatically sanitize data to prevent XSS:
// Text content - escape HTML automatically
modal.bindData({
name: '<script>alert("XSS")</script>' //will be displayed as plain text
});
// HTML content - Delete scripts and event handlers
modal.bindData({
richContent: '<p onclick="alert()">Safe HTML</p>' // Remove onclick
});
// URL - validate protocols
modal.bindData({
link: 'javascript:alert()' // will change to '#'
});Lifecycle Callbacks
Modal has callbacks for each step of the lifecycle:
onShow - Before Modal Shows
const modal = new Modal({
title: 'Loading...',
content: '<div class="spinner"></div>',
onShow: function() {
console.log('Modal is showing');
// Prepare data, start loading
}
});onShown - After Modal Shown
const modal = new Modal({
title: 'User Data',
content: '<div id="user-data"></div>',
onShown: async function() {
console.log('Modal shown');
// Load data, focus input
const data = await fetchUserData();
this.bindData(data);
}
});onHide - Before Modal Hides
const modal = new Modal({
title: 'Form',
content: '<form>...</form>',
onHide: function() {
console.log('Modal is hiding');
// Persist temporary state or cleanup here
saveDraftState();
}
});Note: onHide is a notification hook in the current implementation. Its return value is ignored, so confirmation logic must happen before you call hide().
onHidden - After Modal Hidden
const modal = new Modal({
title: 'Success',
content: '<p>Data saved successfully</p>',
onHidden: function() {
console.log('Modal hidden');
// Cleanup, redirect, refresh data
window.location.reload();
}
});Accessibility
Modal includes comprehensive accessibility features:
ARIA Attributes
// Modal sets ARIA attributes automatically
const modal = new Modal({
title: 'Information',
content: '<p>Content</p>'
});
// Creates HTML like:
// <div role="dialog" aria-modal="true" aria-hidden="false">
// ...
// </div>Inert Property
// Modal uses inert property to prevent interaction when hidden
modal.hide(); // modal.inert = true
modal.show(); // modal.inert = falseFocus Management
const modal = new Modal({
title: 'Form',
content: `
<input type="text" id="name">
<button>Save</button>
`,
focus: true // Enable focus management (default: true)
});
modal.show();
// Automatically focuses first focusable elementFocus Trap
// Modal traps focus within modal
// Tab cycles through modal elements only
// Shift+Tab cycles backwardsKeyboard Navigation
const modal = new Modal({
keyboard: true // Enable keyboard support (default: true)
});
// Press ESC to close modalJavaScript API
Constructor
const modal = new Modal(options);Parameters:
options(Object) - Modal configuration options
Returns:
- Modal instance
show()
Show the modal
modal.show();Returns: void
hide()
Hide the modal
modal.hide();Returns: void
Note: After the close animation finishes, hide() clears the current modal body content and runs cleanup for forms and enhanced elements inside the body.
setContent(content)
Change modal content
// HTML string
modal.setContent('<p>New content</p>');
// DOM node
const element = document.createElement('div');
element.textContent = 'New content';
modal.setContent(element);
// DocumentFragment
const fragment = document.createDocumentFragment();
// ... add nodes to fragment
modal.setContent(fragment);Parameters:
content(string|Node|DocumentFragment) - New content
Returns: void
setTitle(title)
Change modal title
modal.setTitle('New Title');Parameters:
title(string) - New title
Returns: void
Note: setTitle() only updates an existing .modal-title. If the modal was created with an empty title, it does not create a new header block.
bindData(data)
Bind data to modal elements
modal.bindData({
name: 'John Doe',
email: 'john@example.com'
});Parameters:
data(Object) - Data to bind
Returns: Modal instance (for method chaining)
updateData(data)
Update bound data
modal.updateData({
email: 'newemail@example.com'
});Parameters:
data(Object) - Data to update
Returns: Modal instance (for method chaining)
getData()
Get bound data
const data = modal.getData();
console.log(data);Returns: Object - Bound data
destroy()
Remove modal from DOM
modal.destroy();Returns: void
Note: destroy() removes the modal element and clears bound data, but it does not close the backdrop by itself. Prefer calling it from onHidden() after hide().
Examples
1. User Profile Modal
// Create modal
const userModal = new Modal({
id: 'user-modal',
title: 'User Profile',
content: `
<div class="user-profile">
<img data-modal-target="avatar" class="avatar">
<div class="user-info">
<h3 data-modal-target="name"></h3>
<p data-modal-target="email"></p>
<p data-modal-target="role"></p>
</div>
</div>
`,
className: 'modal-user-profile'
});
// Bind data and show
userModal.bindData({
avatar: '/images/users/john.jpg',
name: 'John Doe',
email: 'john@example.com',
role: 'Administrator'
}).show();2. Confirmation Modal
function confirmDelete(itemName, onConfirm) {
const modal = new Modal({
title: 'Confirm Delete',
content: `
<p>Are you sure you want to delete "<strong>${itemName}</strong>"?</p>
<p class="text-muted">This action cannot be undone</p>
<div class="modal-actions">
<button class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button class="btn btn-danger" id="confirm-delete">Delete</button>
</div>
`,
className: 'modal-confirm',
backdrop: true,
keyboard: true,
onShown: function() {
// Bind confirm button
document.getElementById('confirm-delete').addEventListener('click', () => {
onConfirm();
this.hide();
});
// Bind cancel button
document.querySelector('[data-dismiss="modal"]').addEventListener('click', () => {
this.hide();
});
}
});
modal.show();
}
// Usage
confirmDelete('Product #123', () => {
console.log('Item deleted');
// Perform actual deletion
});3. Image Modal
function showImageModal(imageUrl, title, description) {
const modal = new Modal({
title: title,
content: `
<div class="image-modal">
<img data-modal-target="image" class="modal-image">
<p data-modal-target="description" class="image-description"></p>
</div>
`,
className: 'modal-image-viewer image-modal-container'
});
modal.bindData({
image: imageUrl,
description: description
}).show();
}
// Usage
showImageModal(
'/images/product.jpg',
'New Product',
'Product details...'
);4. API Data Modal
async function showUserDetails(userId) {
// Create modal with loading state
const modal = new Modal({
title: 'Loading...',
content: '<div class="spinner">Loading...</div>',
className: 'modal-api-panel'
});
modal.show();
try {
// Load data
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
// Update content
modal.setTitle('User Information');
modal.setContent(`
<div class="user-details">
<img data-modal-target="avatar" class="avatar">
<h3 data-modal-target="name"></h3>
<p data-modal-target="email"></p>
<p data-modal-target="bio"></p>
</div>
`);
// Bind data
modal.bindData(user.data);
} catch (error) {
modal.setTitle('Error');
modal.setContent('<p class="error">Failed to load data</p>');
}
}
// Usage
showUserDetails(123);5. Form Modal
function showEditForm(userData) {
const modal = new Modal({
title: 'Edit Information',
content: `
<form id="edit-form">
<div class="form-group">
<label>Name:</label>
<input type="text" name="name" data-modal-target="name" class="form-control">
</div>
<div class="form-group">
<label>Email:</label>
<input type="email" name="email" data-modal-target="email" class="form-control">
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn btn-secondary" data-dismiss>Cancel</button>
</div>
</form>
`,
className: 'modal-form-panel',
onShown: function() {
const form = document.getElementById('edit-form');
// Bind form submit
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData);
// Save data
await saveUserData(data);
this.hide();
});
// Bind cancel button
form.querySelector('[data-dismiss]').addEventListener('click', () => {
this.hide();
});
}
});
// Bind initial data
modal.bindData(userData).show();
}
// Usage
showEditForm({
name: 'John Doe',
email: 'john@example.com'
});.modal.modal-user-profile .modal-dialog {
max-width: 500px;
}
.modal.modal-confirm .modal-dialog {
max-width: 400px;
}
.modal.modal-image-viewer .modal-dialog {
max-width: min(90vw, 800px);
}
.modal.modal-api-panel .modal-dialog {
max-width: 600px;
}
.modal.modal-form-panel .modal-dialog {
max-width: 500px;
}API Reference
Properties
| Property | Type | Description |
|---|---|---|
id |
string | Modal element ID |
modal |
HTMLElement | Modal element |
dialog |
HTMLElement | Modal dialog element |
header |
HTMLElement|undefined | Modal header element. Created only when title is not empty |
body |
HTMLElement | Modal body element |
visible |
boolean | Visibility state |
boundData |
Object | Bound data |
backdropId |
string|null | Backdrop ID |
Methods
Constructor
new Modal(options)Public Methods
show()- Show modalhide()- Hide modalsetContent(content)- Change contentsetTitle(title)- Change titlebindData(data)- Bind dataupdateData(data)- Update datagetData()- Get datadestroy()- Remove modal
Private Methods
createModal()- Create modal DOMbindEvents()- Bind event handlers_cleanupModalElements()- Cleanup elements_scanModalElements()- Re-scan modal content for managed elements and forms_setElementContent(element, value, key)- Set element content_sanitizeValue(value, context)- Sanitize value_sanitizeText(text)- Sanitize text_sanitizeHTML(html)- Sanitize HTML_sanitizeURL(url)- Sanitize URL_bindCloseButtons(container)- Bind close button handlers in a container
Best Practices
✅ Do
1. Use Data Binding Instead of Direct Manipulation
// ✅ Good - use data binding
modal.bindData({
name: userName,
email: userEmail
});
// ❌ Bad - direct manipulation
modal.body.querySelector('.name').textContent = userName;
modal.body.querySelector('.email').textContent = userEmail;2. Use Lifecycle Callbacks
// ✅ Good - use callbacks
const modal = new Modal({
content: '<div id="data"></div>',
onShown: async function() {
const data = await fetchData();
this.bindData(data);
}
});
// ❌ Bad - load before showing
const data = await fetchData();
modal.bindData(data);
modal.show();3. Clean Up When Done
// ✅ Good - hide first, then destroy after closing
const modal = new Modal({
onHidden() {
this.destroy();
}
});
modal.hide();
// ❌ Bad - destroy while the modal is still visible
modal.destroy();4. Use Method Chaining
// ✅ Good - chain methods
modal.bindData(userData).show();
// ❌ Bad - separate lines
modal.bindData(userData);
modal.show();❌ Don't
1. Don't Insert Unsafe HTML Directly
// ❌ Dangerous - XSS vulnerability
modal.setContent(`<p>${userInput}</p>`);
// ✅ Safe - use data binding
modal.setContent('<p data-modal-target="message"></p>');
modal.bindData({ message: userInput });2. Don't Stack Multiple Modals
// ❌ Bad - stacked modals
modal1.show();
modal2.show(); // Will overlap
// ✅ Good - close before opening new
modal1.hide();
setTimeout(() => modal2.show(), 200);3. Don't Forget Event Listener Cleanup
// ❌ Bad - memory leak
const modal = new Modal({
onShown: function() {
document.getElementById('btn').addEventListener('click', handler);
// No listener removal
}
});
// ✅ Good - cleanup listeners
const modal = new Modal({
onShown: function() {
const btn = document.getElementById('btn');
btn.addEventListener('click', handler);
},
onHidden: function() {
const btn = document.getElementById('btn');
btn.removeEventListener('click', handler);
}
});💡 Tips
1. Use CSS Classes For Styling And Sizing
const modal = new Modal({
className: 'modal-large modal-primary',
content: '...'
});.modal.modal-large .modal-dialog {
max-width: 720px;
}2. Use Template Literals for Complex Content
const modal = new Modal({
content: `
<div class="complex-content">
<section>...</section>
<section>...</section>
</div>
`
});3. Rebuild Content Before Reusing An Instance
const detailsModal = new Modal({
title: 'Details'
});
function openDetails(message) {
detailsModal.setContent('<p data-modal-target="message"></p>');
detailsModal.bindData({ message }).show();
}Because hide() clears the current modal body, reusing an instance usually requires setContent() before the next show().
See Also
- ModalDataBinder - Declarative modal data binding
- BackdropManager - Backdrop management
- DialogManager - General dialog management