Dialogs
Summary
Modal dialogs that block the page until the user responds. Four sizes (small / medium / large / xlarge), header-actions / secondary-content / footer-hint composition slots, and a strict modal mode that ignores Esc and backdrop clicks. Focus is trapped inside the dialog while open and restored to the previously-focused element on close.
When to use
| Variant | Use it for |
|---|---|
Small.neura-dialog2-small | 400px: confirmation dialogs ("Are you sure?"). |
Medium.neura-dialog2-medium | 600px: most common. Forms with up to ~6 fields. |
Large.neura-dialog2-large | 800px: multi-section forms, tables of options, choosers. |
XLarge.neura-dialog2-xlarge | 980px: workflows that approach a full page. |
Strict modaldata-neura-modal="true" | Disables Esc + backdrop dismiss. Use for destructive confirmations or required acknowledgements. |
Examples
Click any button below to open the dialog. All five use the
same Neura.dialog2(elOrSelector).show() API; the
third demo also exercises header actions, secondary content,
and the footer hint, and the last two compose sidebar
navigation inside the content.
Standard dialog
Confirm dialog: Esc and backdrop click both close it.
Strict modal
Esc and backdrop dismissal are disabled; only the explicit Close button works.
Full surface
Header actions, secondary search input, footer hint.
Dialog with sidebar navigation
For a multi-pane dialog (settings pages with a vertical menu on the left), compose vertical tabs inside the dialog content: arrow-key navigation included, no extra JS (tabs auto-init).
Dialog with vertical navigation
The same vertical navgroup used for page navigation also works as a dialog sidebar; nav items are links, so pane switching is your own small click handler (this demo wires the snippet from the JavaScript section):
HTML
Minimum dialog markup (header with title, content, footer with actions):
<section class="neura-dialog2 neura-dialog2-medium" id="confirm" aria-hidden="true">
<header class="neura-dialog2-header">
<h1 class="neura-dialog2-header-main">Confirm</h1>
<button class="neura-dialog2-header-close" aria-label="Close">
<span class="neura-icon neura-icon-close"></span>
</button>
</header>
<div class="neura-dialog2-content">
<p>Are you sure you want to proceed? Esc and clicking the backdrop both close this dialog.</p>
</div>
<footer class="neura-dialog2-footer">
<div class="neura-dialog2-footer-actions">
<button class="neura-button neura-button-primary" id="confirm-ok">Confirm</button>
<button class="neura-button neura-button-link" id="confirm-cancel">Cancel</button>
</div>
</footer>
</section>
Strict modal: add data-neura-modal="true". Esc + backdrop dismiss are disabled and the close button is hidden:
<section class="neura-dialog2 neura-dialog2-small"
id="confirm-destructive"
aria-hidden="true"
data-neura-modal="true">
<!-- ... no .neura-dialog2-header-close ... -->
</section>
Full surface (header actions, header secondary content, footer hint):
<section class="neura-dialog2 neura-dialog2-medium" id="search" aria-hidden="true">
<header class="neura-dialog2-header">
<h1 class="neura-dialog2-header-main">Find files</h1>
<div class="neura-dialog2-header-actions">
<button class="neura-button">Filter</button>
</div>
<div class="neura-dialog2-header-secondary">
<input type="text" placeholder="Search...">
</div>
<button class="neura-dialog2-header-close" aria-label="Close">
<span class="neura-icon neura-icon-close"></span>
</button>
</header>
<div class="neura-dialog2-content"> ... </div>
<footer class="neura-dialog2-footer">
<div class="neura-dialog2-footer-actions">
<button class="neura-button neura-button-primary">Apply</button>
</div>
<div class="neura-dialog2-footer-hint">Press Enter to apply</div>
</footer>
</section>
Sidebar navigation: put vertical tabs inside the content; the tab menu becomes the left panel menu (tabs auto-init, so no extra JS):
<section class="neura-dialog2 neura-dialog2-large" id="settings-dialog" aria-hidden="true">
<header class="neura-dialog2-header">
<h1 class="neura-dialog2-header-main">Project settings</h1>
<button class="neura-dialog2-header-close" aria-label="Close">
<span class="neura-icon neura-icon-close"></span>
</button>
</header>
<div class="neura-dialog2-content">
<div class="neura-tabs neura-tabs-vertical">
<ul class="neura-tabs-menu">
<li class="neura-tabs-menu-item active-tab"><a href="#dlg-general"><strong>General</strong></a></li>
<li class="neura-tabs-menu-item"><a href="#dlg-permissions"><strong>Permissions</strong></a></li>
<li class="neura-tabs-menu-item"><a href="#dlg-notifications"><strong>Notifications</strong></a></li>
</ul>
<div id="dlg-general" class="neura-tabs-pane active-pane">
<p>General settings - project name, description, avatar.</p>
</div>
<div id="dlg-permissions" class="neura-tabs-pane">
<p>Permissions - who can view, edit, and administer.</p>
</div>
<div id="dlg-notifications" class="neura-tabs-pane">
<p>Notifications - events that trigger email and in-app alerts.</p>
</div>
</div>
</div>
<footer class="neura-dialog2-footer">
<div class="neura-dialog2-footer-actions">
<button class="neura-button neura-button-primary" id="settings-save">Save</button>
<button class="neura-button neura-button-link" id="settings-cancel">Cancel</button>
</div>
</footer>
</section>
Vertical-navgroup variant - the content is a flex row: the vertical navgroup as a fixed-width sidebar, panes in the growing cell. Nav items are links, so pane switching is a small consumer-side handler (below):
<div class="neura-dialog2-content" style="display: flex; padding: 0">
<nav class="neura-navgroup neura-navgroup-vertical"
style="flex: 0 0 180px; border-inline-end: 1px solid var(--neura-border-color);
padding-block: 20px; padding-inline: 20px 10px">
<div class="neura-navgroup-inner">
<ul class="neura-nav">
<li class="neura-nav-selected"><a href="#" data-pane="prefs-profile" aria-current="page">Profile</a></li>
<li><a href="#" data-pane="prefs-account">Account</a></li>
<li><a href="#" data-pane="prefs-security">Security</a></li>
</ul>
</div>
</nav>
<div style="flex: 1; padding: 20px">
<div id="prefs-profile">…</div>
<div id="prefs-account" class="neura-hidden">…</div>
<div id="prefs-security" class="neura-hidden">…</div>
</div>
</div>
// Pane switching for the navgroup sidebar (this demo runs exactly this):
document.querySelectorAll('#prefs-dialog [data-pane]').forEach((link) => {
link.addEventListener('click', (e) => {
e.preventDefault();
document.querySelectorAll('#prefs-dialog [data-pane]').forEach((l) => {
l.parentElement.classList.toggle('neura-nav-selected', l === link);
if (l === link) l.setAttribute('aria-current', 'page');
else l.removeAttribute('aria-current');
document.getElementById(l.dataset.pane).classList.toggle('neura-hidden', l !== link);
});
});
});
CSS classes
| Class | Effect |
|---|---|
.neura-dialog2 | Root. Required. |
.neura-dialog2-{small,medium,large,xlarge} | Width. Choose one. |
.neura-dialog2-header | Top bar: 56px tall, gray background. |
.neura-dialog2-header-main | Title: an <h1> (renders at 24px). |
.neura-dialog2-header-actions | Optional action(s) inline with the title (e.g. an extra button). |
.neura-dialog2-header-secondary | Optional right-aligned content (search input, status indicator). |
.neura-dialog2-header-close | Top-right close affordance. Auto-bound to .hide(). |
.neura-dialog2-content | Scrollable body. Min-height varies by size. |
.neura-dialog2-footer | Bottom bar: 51px tall, gray background. |
.neura-dialog2-footer-actions | Right-aligned action buttons. |
.neura-dialog2-footer-hint | Left-aligned hint text (gray). |
Attributes
| Attribute | Effect |
|---|---|
aria-hidden="true" | Initial state: required so the dialog stays hidden until show(). |
data-neura-modal="true" | Strict modal: Esc and backdrop click dismissal are disabled, close button is hidden. |
JavaScript
Dialogs are imperative; there is no auto-init. You call
Neura.dialog2(elOrSelector).show() when you're
ready. Repeated calls return the same singleton instance.
API
| Member | Description |
|---|---|
Neura.dialog2(elOrSelector) | Get / create the singleton for a dialog element. Idempotent. |
.show() | Open the dialog. Pushes the modal blanket, traps focus, sets aria-hidden="false". |
.hide() | Close. Restores focus to the previously-focused element. |
.toggle() | Open if closed, close if open. |
.remove() | Close the dialog, forget the singleton, and remove the element from the DOM. For dialogs you build dynamically and won't reuse. |
.on('show' | 'hide', fn) | Subscribe to lifecycle events (see Events below). |
.off(event, fn) | Unsubscribe. |
.isOpen | Property: true while the dialog is open. |
Events
Lifecycle events are DOM CustomEvents dispatched on the dialog
element. They bubble and are not cancelable; to prevent a
close, use a strict modal (data-neura-modal="true")
with a guarded button instead (see the JavaScript pattern
below).
| Event | Fires |
|---|---|
neura-dialog-show | After the dialog opens: blanket pushed, focus trapped and moved inside. .on('show', fn) listens for this. |
neura-dialog-hide | After the dialog closes and focus is restored to the previously-focused element. .on('hide', fn) listens for this. |
// Per-dialog:
Neura.dialog2('#confirm').on('show', () => console.log('opened'));
// Every dialog on the page - the events bubble, so one document-level
// listener replaces AUI's global AJS.dialog2.on(...) bus:
document.addEventListener('neura-dialog-show', (e) => {
console.log('dialog opened:', e.target.id);
});
Open from a button, close on confirm + cancel:
document.getElementById('open-confirm').addEventListener('click', () => {
Neura.dialog2('#confirm').show();
});
document.getElementById('confirm-ok').addEventListener('click', () => {
Neura.dialog2('#confirm').hide();
// ... commit the action ...
});
document.getElementById('confirm-cancel').addEventListener('click', () => {
Neura.dialog2('#confirm').hide();
});
Submit a form inside a dialog and close on success:
document.getElementById('edit-form').addEventListener('submit', async (e) => {
e.preventDefault();
const ok = await fetch('/save', { method: 'POST', body: new FormData(e.currentTarget) });
if (ok.ok) Neura.dialog2('#edit-dialog').hide();
});
Lazy-load contents on first show, then cache:
Neura.dialog2('#help-dialog').on('show', async () => {
const dlg = document.getElementById('help-dialog');
if (dlg.dataset.loaded) return;
const html = await fetch('/help/keyboard-shortcuts').then((r) => r.text());
dlg.querySelector('.neura-dialog2-content').innerHTML = html;
dlg.dataset.loaded = '1';
});
Confirm-on-close pattern (don't close while there are unsaved changes):
// Mark the dialog data-neura-modal="true" so Esc and backdrop clicks
// can't bypass the check, then close only through a guarded button:
const form = document.getElementById('edit-form');
let dirty = false;
form.addEventListener('input', () => { dirty = true; });
document.getElementById('edit-close').addEventListener('click', () => {
if (dirty && !confirm('Discard your changes?')) return;
dirty = false;
Neura.dialog2('#edit-dialog').hide();
});
AUI compatibility
Every .aui-dialog2* selector aliases to the Neura
rules. AJS.dialog2(...) is a passthrough to
Neura.dialog2, so the full instance surface above
(show/hide/toggle/remove/on/off)
is available to legacy callers. AUI's global event bus
(AJS.dialog2.on("show", fn)) is not provided; the
document-level listener shown under Events covers the same use
case. The shim also mirrors data-aui-modal →
data-neura-modal so strict-modal markup ports
unchanged.
Legacy AJS.Dialog (v1) builder
The v1 builder API is re-implemented on top of
Neura.dialog2 for code still using it, including
the multi-panel page menu (the sidebar-navigation examples
above show the Neura-native way to build that layout):
| Member | Behavior in the shim |
|---|---|
new AJS.Dialog(width, height, id) | Builds a dialog2 shell sized to the given dimensions. |
.addHeader(title) | Header with title. |
.addPanel(name, html) / .addPage() | Content panels; two or more panels on a page render the vertical panel menu on the left. |
.addSubmit(label, fn) / .addButton(label, fn) / .addCancel(label, fn) / .addLink(label, href) | Footer actions (cancel defaults to hiding the dialog). |
.gotoPanel(i) / .gotoPage(i) / .nextPage() / .prevPage() | Panel / page navigation. |
.show() / .hide() / .remove() | Lifecycle, same semantics as dialog2. |
.get() / .getPage(i) / .getPanel(i) / .getCurrentPanel() / .getCurPanel() | Element / panel accessors. |
.enable() / .disable() / .updateHeight() / .isMaximised() / .addButtonPanel() | Kept for API compatibility; no-ops (CSS handles sizing; isMaximised() returns false). |