Modal/dialog window.
See smashingmagazine.com article and nathansmith/cta-modal.
See substrate-system.github.io/modal.
npm i -S @substrate-system/dialogNote
You should prevent the flash of undefined custom elements, because the modal content should be hidden from the user until it is opened. See abeautifulsite.net.
In the example, we have:
import { ModalWindow } from '@substrate-system/dialog'
(async () => {
await Promise.race([
// Load all custom elements
Promise.allSettled([
customElements.whenDefined(ModalWindow.TAG), // modal-window
]),
// Resolve after two seconds
new Promise(resolve => setTimeout(resolve, 2000))
])
// Remove the class, showing the page content
document.body.classList.remove('reduce-fouce')
})()And the HTML has a class reduce-fouce:
<body class="reduce-fouce">The CSS:
body {
&.reduce-fouce {
opacity: 0;
}
}Customize the CSS with some variables.
:root {
/* Overlay */
--modal-overlay-z-index: 100000;
--modal-overlay-background-color: rgba(0, 0, 0, 0.5);
--modal-overlay-padding-top: 20px;
--modal-overlay-padding-left: 20px;
--modal-overlay-padding-right: 20px;
--modal-overlay-padding-bottom: 20px;
/* Dialog */
--modal-dialog-background-color: #fff;
--modal-dialog-border-radius: 0;
--modal-dialog-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.5);
--modal-dialog-padding-top: 20px;
--modal-dialog-padding-left: 20px;
--modal-dialog-padding-right: 20px;
--modal-dialog-padding-bottom: 20px;
--modal-dialog-width: 500px;
/* Close button */
--modal-close-color: #fff;
--modal-close-background-color: #000;
--modal-close-border-radius: 50%;
--modal-close-box-shadow: 0 0 0 1px #fff;
--modal-close-display: block;
--modal-close-font-family: 'Arial', sans-serif;
--modal-close-font-size: 23px;
--modal-close-line-height: 26px;
--modal-close-width: 26px;
/* Close button hover/focus */
--modal-close-color-hover: #000;
--modal-close-background-color-hover: #fff;
--modal-close-box-shadow-hover: 0 0 0 1px #000;
}Just import. This calls the global function window.customElements.define.
import '@substrate-system/dialog'Then use the tag in HTML:
<button id="open-modal" type="button">Open modal</button>
<modal-window id="my-modal">
<h2>Modal Title</h2>
<p>This is the modal content.</p>
<p>Click the X, press Escape, or click outside to close.</p>
</modal-window>Open/close via JavaScript:
const modal = document.getElementById('my-modal')
document.getElementById('open-modal').addEventListener('click', () => {
modal.open()
})First copy the file to a location accessible to your web server.
cp ./node_modules/@substrate-system/dialog/dist/index.min.js ./public/dialog.jsThen link to the file in HTML
<body>
<p>...content...</p>
<script type="module" src="/dialog.js"></script>
</body>Controls whether the modal is open. Set to "true" to open, "false" or remove the attribute to close.
modal.setAttribute('active', 'true') // open
modal.setAttribute('active', 'false') // close
modal.removeAttribute('active') // closeSet to "false" to prevent the modal from being closed via the close button, Escape key, or clicking outside. You must close it programmatically. Defaults to true.
<modal-window closable="false">
<h2>Unclosable Modal</h2>
<p>This modal cannot be closed with the X button, Escape key, or clicking outside.</p>
<button id="close-btn" type="button">Close this modal</button>
</modal-window>document.getElementById('close-btn').addEventListener('click', () => {
modal.close()
})Hides the close button icon. Useful when you want to provide your own close UI.
<modal-window no-icon>
<header>
<button type="button" id="cancel">Cancel</button>
<h3>Edit profile</h3>
<button type="button" id="save">Save</button>
</header>
<div>...form content...</div>
</modal-window>Controls whether open/close animations are used. Set to "false" to disable. Defaults to true. Animations also respect prefers-reduced-motion.
<modal-window animated="false">
<p>No animation</p>
</modal-window>When set to "true", clicking outside the modal does not close it. The Escape key and close button still work (unless closable="false").
<modal-window static="true">
<p>Click outside won't close this</p>
</modal-window>When present, clicking the backdrop does not close the modal. Unlike static, this is a boolean attribute (no value needed). The Escape key and close button still work.
<modal-window noclick>
<p>Clicking the backdrop won't close this</p>
</modal-window>Sets the title/aria-label for the close button. Defaults to "Close".
<modal-window close="Dismiss">
<p>Close button will have title "Dismiss"</p>
</modal-window>Opens the modal and focuses it.
const modal = document.querySelector('modal-window')
modal.open()Closes the modal and returns focus to the previously focused element.
modal.close()Things handled by this library:
role="dialog"andaria-modal="true"on the dialog- Focus trapping (Tab cycles within modal)
- Escape key closes the modal (when
closable) - Focus returns to the trigger element on close
- Close button has
aria-label - Respects
prefers-reduced-motion
The component extracts text from the first heading (h1-h6) to use as the
dialog's aria-label. Always include a descriptive heading:
<!-- Good: heading text becomes the aria-label -->
<modal-window>
<h2>Edit Profile</h2>
<p>Update your information below.</p>
</modal-window>
<!-- Avoid: no heading results in aria-label="modal" -->
<modal-window>
<p>Some content without a heading...</p>
</modal-window>For modals with important supplementary text (like warnings), you can
add aria-describedby. This library will handle an aria-describedby attribute
correctly, meaning that it will be forwarded to the correct element.
<modal-window aria-describedby="delete-warning" id="confirm-delete">
<h2>Delete Account</h2>
<p id="delete-warning">
This action cannot be undone.
</p>
<button type="button">Cancel</button>
<button type="button">Delete</button>
</modal-window>Thanks @nathansmith and Smashing Magazine for publishing this originally.