Blocking modal dialogs for focused tasks, forms, and multi-step flows. Styles live in scss/components/_modal.scss. For destructive confirmations with limited choices, use alert dialog on the Alerts page. For layout blocks in the page, see Cards.
Dialog pattern
Overlay with role="dialog", aria-modal="true", labelled title, optional description, scrollable body, and footer actions. The root stays in the DOM with hidden when closed.
Modal vs alert dialog
- Modal (
.modal), general content and forms. Backdrop dismiss is optional (data-modal-static). - Alert dialog (
.alert-dialog), urgent decisions with limited actions. See Alerts.
Invite teammates
Share this workspace with your team. They will receive an email invitation with a secure link.
Edit profile
Required fields are marked. Backdrop click is disabled so drafts are not lost.
Archive item?
You can restore archived items within 30 days.
<div class="modal" id="invite-modal" data-modal-live="" hidden="">
<div class="modal__backdrop" data-modal-dismiss=""></div>
<div class="modal__panel" role="dialog" aria-modal="true" aria-labelledby="invite-title">
<header class="modal__header">
<h2 class="modal__title heading-h5" id="invite-title">Invite teammates</h2>
<button type="button" class="modal__close" data-modal-close="" aria-label="Close">×</button>
</header>
<div class="modal__body">
<p class="body-medium">Message…</p>
</div>
<footer class="modal__footer"><button type="button" class="btn btn--outline" data-modal-close="">Cancel</button><button type="button" class="btn btn--primary" data-modal-close="">Send</button></footer>
</div>
</div>
Sizes and static backdrop
Default width uses $modal-max-width (32rem). Add modal--sm (24rem) or modal--lg (42rem) on the root. Set data-modal-static on the root to require an explicit close (no backdrop dismiss) for forms or drafts.
Behavior in your app
Open with data-modal-open="{id}", close with data-modal-close or backdrop data-modal-dismiss. Copy docs-site/src/lib/modal-behavior.ts for focus trap, Escape, scroll lock, and focus restore.
import { wireAllModals } from './modal-behavior';
document.addEventListener('DOMContentLoaded', () => wireAllModals());
Class reference
| Class / attribute | Description |
|---|---|
.modal | Fixed overlay root (hidden when closed) |
.modal__panel | Dialog surface (role="dialog") |
.modal__header / .modal__body / .modal__footer | Regions (title, scrollable content, actions) |
.modal--sm / .modal--lg | Width presets |
data-modal-open | Opens modal by id |
data-modal-close | Closes modal |
data-modal-dismiss | Backdrop click closes (omit with data-modal-static) |
data-modal-live | Wires reference behavior script |
Customize (Sass)
-
Modal width and layering
Override modal tokens before importing components.@use "pimentcss-design-system" with ( $modal-max-width: 36rem, $modal-max-width-lg: 48rem, $modal-z: 1300 ); -
Rebuild CSS
Run after editing _modal.scss.npm run build:css
Accessibility (RGAA / WCAG)
Modals
- Role, use
role="dialog"(notalertdialog) for general tasks. Reservealertdialogfor urgent, limited-choice prompts. - Naming,
aria-labelledbypoints to the visible title; addaria-describedbywhen extra context is needed. - Focus, move focus into the panel on open, trap Tab inside, restore focus to the trigger on close. Escape closes unless
data-modal-no-escape. - Scroll, lock page scroll (
body.modal-open) while open; keep long content in.modal__body.