Skip to content

Modal

A modal is an overlay dialog that focuses the user’s attention on a specific task or content.

<div class="tng-modal">
<div class="tng-modal-panel">
<button class="tng-button is-tertiary is-ghost">
<span>Close</span>
<i class="tng-icon icon-close" aria-hidden="true"></i>
</button>
<div class="tng-modal-content">
<div class="tng-slot is-primary"></div>
</div>
</div>
</div>

You can use the positioning utilities to create a fullscreen modal.

<div class="tng-modal | p-absolute at-maximum">
<div class="tng-modal-panel">
<button class="tng-button is-tertiary is-ghost">
<span>Close</span>
<i class="tng-icon icon-close" aria-hidden="true"></i>
</button>
<div class="tng-modal-content">
<div class="tng-slot is-primary"></div>
</div>
</div>
</div>

When a modal contains potentially more content than fits in the viewport, you can use the overflow utility to make the content area scrollable.

<div class="tng-modal" style="max-block-size: 300px">
<div class="tng-modal-panel">
<button class="tng-button is-tertiary is-ghost">
Close
<i class="tng-icon icon-close" aria-hidden="true"></i>
</button>
<div class="tng-overflow-scroll is-block">
<div class="tng-modal-content">
<p class="tng-text-body"></p>
<p class="tng-text-body"></p>
</div>
</div>
</div>
</div>

Ea reprehenderit qui mollit. Magna quis veniam ea est deserunt labore eu ullamco ea ex cupidatat ullamco exercitation esse. Anim amet mollit qui mollit reprehenderit nisi commodo occaecat. Laboris esse ex amet consequat proident officia id velit cupidatat nostrud deserunt ipsum proident. Enim exercitation qui sunt.

Ea reprehenderit qui mollit. Magna quis veniam ea est deserunt labore eu ullamco ea ex cupidatat ullamco exercitation esse. Anim amet mollit qui mollit reprehenderit nisi commodo occaecat. Laboris esse ex amet consequat proident officia id velit cupidatat nostrud deserunt ipsum proident. Enim exercitation qui sunt.

<div class="tng-modal">
<div class="tng-frame">
<div class="tng-slot"></div>
</div>
<div class="tng-modal-panel">
<button class="tng-button is-tertiary is-ghost">
Close
<i class="tng-icon icon-close" aria-hidden="true"></i>
</button>
<div class="tng-modal-content">
<div class="tng-slot is-primary"></div>
</div>
</div>
</div>
source code

Always render a modal on a <dialog> element and open it with showModal(). This gives you built-in browser behaviour that is difficult to replicate manually:

  • Promotes the dialog to the top layer — no z-index conflicts.
  • Renders a ::backdrop pseudo-element automatically (.tng-modal on a <dialog> applies the tng-backdrop background).
  • Traps focus inside the dialog while it is open.
  • Returns focus to the previously focused element on close.
  • Closes on Escape by default.
AttributePurpose
aria-labelledbyPoint to the modal’s visible title so assistive technologies announce it.
aria-describedbyOptionally point to a description paragraph for additional context.
aria-controlsSet on the trigger button, referencing the dialog’s id.
aria-modal="true"Implied automatically when using showModal() — do not set it yourself.

When a modal is open the rest of the page must be unreachable. showModal() handles this for the accessibility tree, but pointer events on the underlying page can still leak through. Set document.body.inert = true while the dialog is open and reset it on close, as shown in the recipe above.

KeyAction
Tab / Shift+TabCycles focus through focusable elements inside the dialog (focus trap is built in with showModal()).
EscapeCloses the dialog (built-in). Listen for the close event to run cleanup.

Wrap the dialog content in a <form method="dialog">. Any <button> inside the form that is not type="button" will close the dialog and its value will be available via dialog.returnValue. This keeps close / confirm actions accessible without custom JavaScript.