Skip to content

substrate-system/dialog

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dialog

tests types module semantic versioning Common Changelog install size GZip size license

Modal/dialog window.

See smashingmagazine.com article and nathansmith/cta-modal.

Contents

Demo

See substrate-system.github.io/modal.

Install

npm i -S @substrate-system/dialog

Use

FOUCE

Note

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;
    }
}

CSS

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;
}

Bundler

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()
})

HTML only

First copy the file to a location accessible to your web server.

cp ./node_modules/@substrate-system/dialog/dist/index.min.js ./public/dialog.js

Then link to the file in HTML

<body>
    <p>...content...</p>
    <script type="module" src="/dialog.js"></script>
</body>

API

Attributes

active

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')        // close

closable

Set 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()
})

no-icon

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>

animated

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>

static

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>

noclick

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>

close

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>

Methods

open()

Opens the modal and focuses it.

const modal = document.querySelector('modal-window')
modal.open()

close()

Closes the modal and returns focus to the previously focused element.

modal.close()

Accessibility

Things handled by this library:

  • role="dialog" and aria-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

Things You Need To Do

Include a heading

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>

Adding a description

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>

Example

See ./example.

Credits

Thanks @nathansmith and Smashing Magazine for publishing this originally.

About

Modal window web component

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •