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

VariantUse 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 modal
data-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

ClassEffect
.neura-dialog2Root. Required.
.neura-dialog2-{small,medium,large,xlarge}Width. Choose one.
.neura-dialog2-headerTop bar: 56px tall, gray background.
.neura-dialog2-header-mainTitle: an <h1> (renders at 24px).
.neura-dialog2-header-actionsOptional action(s) inline with the title (e.g. an extra button).
.neura-dialog2-header-secondaryOptional right-aligned content (search input, status indicator).
.neura-dialog2-header-closeTop-right close affordance. Auto-bound to .hide().
.neura-dialog2-contentScrollable body. Min-height varies by size.
.neura-dialog2-footerBottom bar: 51px tall, gray background.
.neura-dialog2-footer-actionsRight-aligned action buttons.
.neura-dialog2-footer-hintLeft-aligned hint text (gray).

Attributes

AttributeEffect
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

MemberDescription
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.
.isOpenProperty: 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).

EventFires
neura-dialog-showAfter the dialog opens: blanket pushed, focus trapped and moved inside. .on('show', fn) listens for this.
neura-dialog-hideAfter 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-modaldata-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):

MemberBehavior 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).