A component for changing the mode of a page to complete a critical task. This is usually used in conjunction with the dialog component to make modal dialogs.
npm install @vrembem/modal
@use "@vrembem/modal";
import Modal from '@vrembem/modal';
const modal = new Modal({ autoInit: true });
Modals are composed using classes and data attributes for their triggers. The basic structure of a modal is an element with an id
and the modal
class containing a child element with the modal__dialog
class. There are three types of modal triggers, each defined by a data attribute:
-
data-modal-open
: Opens a modal. Should take the id of the modal it's meant to open. Will stack modals if triggered from inside an already opened modal. -
data-modal-close
: Closes a modal. Will close the last opened modal if left value-less. Can also take a modal id to close a specific modal, or"*"
to close all open modals. -
data-modal-replace
: Replaces currently opened modal(s) with the modal of the id provided.
<button data-modal-open="modal-id">...</button>
<div id="modal-id" class="modal">
<div class="modal__dialog" role="dialog" aria-modal="true">
<button data-modal-close>...</button>
...
</div>
</div>
The dialog element of a modal is defined using the modal__dialog
class. Modal dialogs should also be given the role
attribute with a value of either dialog
or alertdialog
and the aria-modal
attribute with a value of true
. Authors should provide modal dialogs with aria-labelledby
and aria-describedby
attributes if applicable to further improve accessibility.
<div id="modal-id" class="modal">
<div class="modal__dialog dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-description">
<div class="dialog__header">
<h2 id="dialog-title">...</h2>
</div>
<div class="dialog__body">
<p id="dialog-description">...</p>
</div>
<div class="dialog__footer">
...
</div>
</div>
</div>
The dialog component is a great fit for composing a modal’s dialog.
Modal dialogs are given focus when opened as long as the setTabindex
option is set to true
or if the modal dialog has tabindex="-1"
set manually. If focus on a specific element inside a modal is preferred, give that element the data-focus
attribute. Focus is returned to the element that initially triggered the modal once closed.
<!-- Focus is returned to the trigger when a modal is closed -->
<button data-modal-open="modal-id">...</button>
<!-- Sets focus to modal dialog when opened -->
<div id="modal-id" class="modal">
<div class="modal__dialog" role="dialog" aria-modal="true" tabindex="-1">
...
</div>
</div>
<!-- Sets focus to data-focus element when opened -->
<div id="modal-id" class="modal">
<div class="modal__dialog" role="dialog" aria-modal="true">
<input data-focus type="text">
...
</div>
</div>
While a modal is active, the contents obscured by the modal are made inaccessible to all users via a focus trap. This means that the TAB
key, and a screen reader’s virtual cursor (arrow keys) should not be allowed to leave the modal dialog and traverse the content outside of the dialog.
To change the selector used in finding the preferred focus element, pass your own selector via the
selectorFocus
option (defaults to'[data-focus]'
).
Required modals are modals that need an explicit action to be closed. That means clicking on the background or pressing the escape key to close a required modal is disabled. Required modals are set by giving a dialog the attribute role
with a value of alertdialog
.
<div id="modal-id" class="modal">
<div class="modal__dialog" role="alertdialog" aria-modal="true">
...
<button data-modal-close>Agree</button>
</div>
</div>
To change the selector used in creating required modals, pass your own selector via the
selectorRequired
option (defaults to'[role="alertdialog"]'
).
Modals on the web have an expected set of patterns that this component follows. Here's what to expect:
- When a modal is opened, focus is moved to the dialog or an element inside.
- Modals provide standard methods for the user to close such as using the
esc
key or clicking outside the dialog. - While the modal is active, contents obscured by the modal are inaccessible to all users.
- When a modal is closed, focus is returned to the initial trigger element that activated the dialog.
To take full advantage of modal's accessibility features, it's recommended to set the selectorInert
option to all elements that are outside the modal. If you have modal markup throughout your document, use the teleport
option to consolidate all modals in the DOM to a single location. All elements that match the selectorInert
selector will be given the inert
attribute as well as aria-hidden="true"
when a modal is opened.
Here's an example where we want the <main>
content area to be inaccessible while modals are open. We also want all modals to be moved outside the main content element using the after
method.
const modal = new Modal({
selectorInert: 'main',
teleport: 'main',
teleportMethod: 'after'
});
await modal.init();
To return a modal to its original location, use the collection API method teleportReturn()
:
// Get an entry from the modal collection.
const entry = modal.get('modal-id');
// Returns the modal to its previously teleported location.
entry.teleportReturn();
Adds styles to a modal that make it fill the entire viewport when opened.
<div id="modal-id" class="modal modal_full">...</div>
The default position of modals is in the center of the viewport. The position modifier allows positioning a modal to the top, bottom, left and right side of the document viewport.
<div id="..." class="modal modal_pos_top">...</div>
<div id="..." class="modal modal_pos_bottom">...</div>
<div id="..." class="modal modal_pos_left">...</div>
<div id="..." class="modal modal_pos_right">...</div>
modal_pos_top
modal_pos_left
modal_pos_right
modal_pos_bottom
Adjusts the size of modals. This modifier provides five options that get built using the $size-scale
variable map.
<div id="modal-id" class="modal modal_size_sm">...</div>
modal_size_xs
modal_size_sm
modal_size_md
modal_size_lg
modal_size_xl
Variable | Default | Description |
---|---|---|
$prefix-block |
null |
String to prefix blocks with. |
$prefix-element |
"__" |
String to prefix elements with. |
$prefix-modifier |
"_" |
String to prefix modifiers with. |
$prefix-modifier-value |
"_" |
String to prefix modifier values with. |
$z-index |
1000 |
Base z-index of modals. Stacked modals are incremented by 1. |
$width |
36em |
The default max width of modals. |
$travel |
5em |
Distance that modal travel during their transition. |
$transition-duration |
core.$transition-duration |
Duration of modal transition. |
$transition-timing-function |
core.$transition-timing-function |
Timing function used for modal transitions. |
$background |
core.$night |
Background color of modal screen. |
$background-alpha |
0.8 |
The alpha channel for the modal screen. |
$box-shadow |
core.$box-shadow-5 |
Box shadow applied to modal dialog elements. |
$outline |
0 solid rgba(core.$primary, 0) |
Outline applied to modal dialog elements. |
$outline-focus |
4px solid rgba(core.$primary, 1) |
Outline applied to modal dialog elements in their focus state. |
$outline-focus-alert |
4px solid rgba(core.$danger, 1) |
Outline applied to required modal dialog elements in their focus state. |
$aside-width |
16em |
Width applied to modals using modal_pos_left and modal_pos_right modifiers. |
$aside-max-width |
90% |
Max width applied to modals using modal_pos_left and modal_pos_right modifiers. |
$size-scale |
Map Ref ↓ | The size scale map the modal_size_[key] modifier uses to build its styles. |
The size scale map the modal_size_[key]
modifier uses to build its styles.
$size-scale: (
'xs': 20em, // 288px
'sm': 24em, // 384px
'md': 36em, // 576px
'lg': 48em, // 768px
'xl': 60em // 960px
) !default;
Key | Default | Description |
---|---|---|
autoInit |
false |
Automatically initializes the instance. |
dataOpen |
'modal-open' |
Data attribute for a modal open trigger. |
dataClose |
'modal-close' |
Data attribute for a modal close trigger. |
dataReplace |
'modal-replace' |
Data attribute for a modal replace trigger. |
dataConfig |
'modal-config' |
Data attribute to find modal specific configuration settings. Value should be a JSON object. |
selectorModal |
'.modal' |
Selector for modal element. |
selectorDialog |
'.modal__dialog' |
Selector for modal dialog element. |
selectorRequired |
'[role="alertdialog"]' |
Selector used to apply required modal state. |
selectorFocus |
'[data-focus]' |
Focus preference selector for when modals are initially opened. |
selectorInert |
null |
Applies inert and aria-hidden attributes to all matching elements when a modal is opened. |
selectorOverflow |
'body' |
Applies overflow:hidden styles on all matching elements when a modal is opened. |
stateOpened |
'is-opened' |
Class used for open state. |
stateOpening |
'is-opening' |
Class used for transitioning to open state. |
stateClosing |
'is-closing' |
Class used for transitioning to closed state. |
stateClosed |
'is-closed' |
Class used for closed state. |
customEventPrefix |
'modal:' |
Prefix to be used on custom events. |
eventListeners |
true |
Whether or not to set the document event listeners on init. |
teleport |
null |
Teleport selector where modals get moved to. Leave as null to disable teleport. |
teleportMethod |
'append' |
Teleport method options include after , before , append and prepend relative to the teleport reference. |
setTabindex |
true |
Whether or not to set tabindex="-1" on all modal dialog elements on init. |
transition |
true |
Toggle the transition animation of modals. |
-
modal:opened
Emits when a modal has opened. -
modal:closed
Emits when a modal has closed.
Returns an array where all modal objects are stored when registered. Each modal object contains the following properties:
{
id: String, // The unique ID of the modal.
state: String, // The current state of the modal ('closing', 'closed', 'opening' or 'opened').
el: HTMLElement, // The modal HTML element.
dialog: HTMLElement // The modal dialog HTML element.
returnRef: HTMLComment // The return reference left when a modal is teleported.
settings: Object // The modal specific settings.
open: Function // Method to open this modal.
close: Function // Method to close this modal.
replace: Function // Method to replace open modal(s) with this modal.
deregister: Function // Method to deregister this modal.
teleport: Function // Method to teleport this modal.
teleportReturn: Function // Method to return this modal to its previous location.
getSetting: Function // Method that returns either a modal specific setting or global modal setting.
}
Returns
-
Array
An array of collection entries.
Returns an array of all currently opened modals. These are sorted in the order they're added to the array (first item was opened first, last item was opened last).
Returns
-
Array
An array of collection entries.
Returns the currently active modal or the modal at the top of the stack if multiple modals are open. Will return undefined
if no modals are open.
Returns
-
Object || undefined
Collection entry.
Initializes the modal instance. During initialization, the following processes are run:
- Register each modal in the collection by running
registerCollection()
. - Sets up global event listeners by running
initEventListeners()
.
Parameters
-
options [Object] (optional)
An options object for passing custom settings.
const modal = new Modal();
await modal.init();
Destroys and cleans up the modal initialization. During cleanup, the following processes are run:
- Deregister the modal collection by running
deregisterCollection()
. - Removes global event listeners by running
destroyEventListeners()
.
const modal = new Modal();
await modal.init();
// ...
await modal.destroy();
Set document event listeners.
const modal = new Modal({ eventListeners: false });
await modal.init();
modal.initEventListeners();
Remove document event listeners.
const modal = new Modal();
await modal.init();
// ...
modal.destroyEventListeners();
Registers a modal into the collection. This also sets the initial state and applies missing accessibility attributes such as role="dialog"
and aria-modal="true"
.
Parameters
-
query [String || Object]
A modal ID or an HTML element of either the modal or its trigger.
Returns
-
Object
The modal object that got stored in the collection.
const result = await modal.register('modal-id');
// => Object { id: 'modal-id', ... }
Deregister the modal from the collection. This closes the modal if it's opened, returns a modal if it has been teleported and removes the entry from the collection.
Parameters
-
query [String || Object]
A modal ID or an HTML element of either the modal or its trigger.
Returns
-
Array
Returns the newly modified collection array.
const result = await modal.deregister('modal-id');
// => Array [{}, {}, ...]
Registers array of modals to the collection. All modals in array are run through the register()
method.
Parameters
-
items [Array]
An array of modals or modal IDs to register.
Returns
-
Array
Returns the collection array.
const modals = document.querySelectorAll('.modal');
const result = await modal.registerCollection(modals);
// => Array [{}, {}, ...]
Deregister all modals in the collections array. All modals in collection are run through the deregister()
method.
Returns
-
Array
Returns the empty collection array.
const result = await modal.registerCollection();
// => Array []
Used to retrieve a registered modal object from the collection. The value should match the key type to search by: e.g. to search by modal elements, pass the modal html node with a key of 'el'
. Defaults to 'id'
.
Parameters
-
value [String || Object]
The value to search for within the collection. -
key [String] (optional) (default 'id')
The property key to search the value against.
Returns
-
Object || undefined
The first element in the collection that matches the provided query and key. Otherwise, undefined is returned.
const entry = modal.get('modal-id');
// => Object { id: 'modal-id', ... }
Opens a modal using the provided ID.
Parameters
-
id [String]
The ID of the modal to open. -
transition [Boolean] (optional)
Whether or not to animate the transition. -
focus [Boolean] (optional)
Whether or not to handle focus management.
Returns
-
Object
The modal object that was opened.
const entry = await modal.open('modal-key');
// => Object { id: 'modal-id', ... }
Closes a modal using the provided ID. Can be called without an ID to close most recently opened modal.
Parameters
-
id [String] (optional)
The ID of the modal to close. -
transition [Boolean] (optional)
Whether or not to animate the transition. -
focus [Boolean] (optional)
Whether or not to handle focus management.
Returns
-
Object
The modal object that was closed.
const entry = await modal.close();
// => Object { id: 'modal-id', ... }
Replaces currently opened modal(s) with the modal of the id provided. This could be used to trigger a modal when modal stacking is not desired.
Parameters
-
id [String]
The ID of the modal to open. Will close all other opened modals. -
transition [Boolean] (optional)
Whether or not to animate the transition. -
focus [Boolean] (optional)
Whether or not to handle focus management.
Returns
-
Object
An object withopened
andclosed
properties whose values will be the opened modal and an array of modals that were closed.
const obj = await modal.replace('modal-id');
// => Object { opened: { id: 'modal-id', ... }, closed: [...] }
Closes all open modals. Will exclude closing a modal using the provided ID.
Parameters
-
exclude [String] (optional)
The ID of a modal to exclude from closing. Will close all other opened modals. -
transition [Boolean] (optional)
Whether or not to animate the transition. -
focus [Boolean] (optional)
Whether or not to handle focus management.
Returns
-
Array
An array of all modals that were closed.
const array = await modal.closeAll();
// => Array [{}, {}, ...]