Modal

Mode

Skin

Theme

Overview & Philosophy

ln-modal is applied to a native <dialog data-ln-modal id="..."> element, paired with trigger buttons (data-ln-modal-for="...") and dismiss controls (data-ln-modal-close). When triggered, it sets data-ln-modal="open", invokes dialog.showModal() for native focus trapping, and locks background scrolling via .ln-modal-open on <body>. On dismiss clicks, backdrop clicks, or pressing Escape, it cleanly closes the dialog, restores keyboard focus, and clears the scroll lock.

In ln-ashlar, this follows the Three-Layer Architecture: ln-modal is a Layer 1 Simple Component that manages the native overlay state and accessibility, while ln-ui-coordinator is the Layer 2 Coordinator that handles triggers, hash navigation (#modalId), record filling (ln-fill), and auto-closing on AJAX success.

The Modal & UI Coordinator Doctrine

The contract is absolute: ln-modal (on <dialog data-ln-modal>) manages ONLY the binary overlay state. ln-ui-coordinator (placed on <dialog data-ln-ui-coordinator> or container) acts as the Layer 2 Coordinator, orchestrating inner forms (<form data-ln-form>), triggers (data-ln-modal-for), deep links (#modalId:param), toasts, and auto-closing on ln-ajax:success.

Focus Trapping & Accessibility (WAI-ARIA)

  • Native Focus Trap: Tabbing cycles keyboard focus strictly inside the native <dialog> when opened via showModal().
  • Background Scroll Lock: Opening a modal suppresses background scrolling by applying the ln-modal-open class to <body>.
  • Escape Key Dismissal: Pressing ESC routes through the attribute synchronizer to ensure before-close lifecycle validation always runs.
  • Focus Return: When closed, keyboard focus returns precisely to the trigger element that launched it.

For the complete developer reference, see the ln-modal README and the ln-ui-coordinator spec.

Examples

Basic Modal

This is a standard basic modal overlay. It traps focus natively, locks background scrolling, and closes on Escape key or backdrop click.

New User Edit User —

Large Modal

A larger modal for more content. Size set via @include modal-lg in SCSS.

Name Status Points
Marko OK 95
Ana OK 88
Ivan Error 42

Tall Content — Scroll Test

This modal has more content than fits in the viewport. Only main should scroll — the header and footer stay sticky.

Destructive Confirm

Delete membership record

The membership record and all associated data will be permanently removed. This action cannot be undone.

Hash addressing & coexistence

Demonstrates codec isolation: ln-tabs and ln-modal share the same URL hash using the ln-core hash codec. Each component reads and writes ONLY its own namespace segment — foreign segments are preserved on every write. The modal's namespace is its id (demo-hash-modal).

Manual tests:

  1. Click Edit #5, switch to the Members tab. The URL becomes #demo-hash-modal:5&demo-tab:members, the modal STAYS open, and the Title field is filled by the ln-ui-coordinator.
  2. Reverse order (regression direction): switch to the Members tab FIRST, then click Edit #5. The URL becomes #demo-tab:members&demo-hash-modal:5 and the tab STAYS on Members — the modal anchor click MERGES its segment instead of replacing the whole fragment.
  3. Reload with #demo-tab:members&demo-hash-modal:42 in the URL. The Members tab should be active AND the modal open with Title "Internal audit log" filled.
  4. With the modal open, press Back → modal closes, tab state intact.

Overview panel content. Switch tabs to see the modal hash segment preserved.

No events yet.

New record Edit record #

Events & Lifecycle

Lifecycle Events

Event Cancelable When detail
ln-modal:before-open ✅ Before opening { modalId, target }
ln-modal:open ❌ Modal opened { modalId, target, hashNs, param } (id modal) / { modalId, target } (no-id modal)
ln-modal:before-close ✅ Before closing { modalId, target }
ln-modal:close ❌ Modal closed { modalId, target }

Live Event Log Demo

No events yet.

Events Demo

Opening and closing this modal dispatches ln-modal:open and ln-modal:close events.