Basic Modal Setup
A zero-dependency modal system controlled entirely by data attributes. Opening a modal sets data-modal-group-status="active" on a shared wrapper and data-modal-status="active" on the target card; closing reverts both. Supports multiple named modals per group, close-on-backdrop-click, close-on-Escape, and any number of [data-modal-target] triggers.
Code
<div data-modal-group-status="not-active" class="modal">
<div data-modal-close="" class="modal__dark"></div>
<div data-modal-name="modal-a" data-modal-status="not-active" class="modal__card">
<div class="modal__scroll">
<div class="modal__content">
<h2 class="modal__h2">Modal A</h2>
<p class="modal__p">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</div>
<div data-modal-close="" class="modal__btn-close"><div class="modal__btn-close-bar"></div><div class="modal__btn-close-bar is--second"></div></div>
</div>
<div data-modal-name="modal-b" data-modal-status="not-active" class="modal__card">
<div class="modal__scroll">
<div class="modal__content">
<h2 class="modal__h2">Modal B</h2>
<p class="modal__p">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</div>
<div data-modal-close="" class="modal__btn-close"><div class="modal__btn-close-bar"></div><div class="modal__btn-close-bar is--second"></div></div>
</div>
</div>
<div data-modal-target="modal-a" data-modal-status="not-active" class="demo-btn"><p class="demo-btn__p">Modal A</p></div>
<div data-modal-target="modal-b" data-modal-status="not-active" class="demo-btn"><p class="demo-btn__p">Modal B</p></div>.modal {
z-index: 100;
opacity: 0;
pointer-events: none;
visibility: hidden;
justify-content: center;
align-items: center;
padding: 2em 1em;
display: flex;
position: fixed;
inset: 0;
overflow: hidden;
transition: all 0.2s linear;
}
.modal[data-modal-group-status="active"] {
opacity: 1;
visibility: visible;
}
.modal__dark {
opacity: .5;
pointer-events: auto;
cursor: pointer;
background-color: #131313;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.modal__card {
pointer-events: auto;
background-color: #efeeec;
border-radius: 2em;
width: 100%;
max-width: 54em;
max-height: 100%;
padding: .75em;
display: none;
position: relative;
}
.modal__card[data-modal-status="active"] {
display: flex;
}
.modal__scroll {
grid-column-gap: 1.5em;
grid-row-gap: 1.5em;
background-color: #e2e1df;
border-radius: 1.25em;
flex-flow: column;
width: 100%;
max-height: 100%;
display: flex;
position: relative;
overflow: scroll;
}
.modal__content {
grid-column-gap: 1.5em;
grid-row-gap: 1.5em;
flex-flow: column;
padding: 2em;
display: flex;
}
.modal__h2 {
margin-top: 0;
margin-bottom: 0;
font-size: 2.5em;
font-weight: 500;
line-height: 1.175;
}
.modal__p {
margin-bottom: 0;
font-size: 1em;
line-height: 1.5;
}
.modal__btn-close {
background-color: #efeeec;
border-radius: 50%;
justify-content: center;
align-items: center;
width: 3.5em;
height: 3.5em;
display: flex;
position: absolute;
top: 2.5em;
right: 2.5em;
}
.modal__btn-close-bar {
background-color: currentColor;
width: .125em;
height: 40%;
position: absolute;
transform: rotate(45deg);
}
.modal__btn-close-bar.is--second {
transform: rotate(-45deg);
}
/* Demo Buttons */
.demo-btn {
background-color: #efeeec;
border-radius: 50em;
justify-content: center;
align-items: center;
display: flex;
position: relative;
}
.demo-btn__p {
margin-bottom: 0;
padding: .65em 1.25em;
font-size: 1em;
}function initModalBasic() {
const modalGroup = document.querySelector('[data-modal-group-status]');
const modals = document.querySelectorAll('[data-modal-name]');
const modalTargets = document.querySelectorAll('[data-modal-target]');
// Open modal
modalTargets.forEach((modalTarget) => {
modalTarget.addEventListener('click', function () {
const modalTargetName = this.getAttribute('data-modal-target');
// Close all modals
modalTargets.forEach((target) => target.setAttribute('data-modal-status', 'not-active'));
modals.forEach((modal) => modal.setAttribute('data-modal-status', 'not-active'));
// Activate clicked modal
document.querySelector(`[data-modal-target="${modalTargetName}"]`).setAttribute('data-modal-status', 'active');
document.querySelector(`[data-modal-name="${modalTargetName}"]`).setAttribute('data-modal-status', 'active');
// Set group to active
if (modalGroup) {
modalGroup.setAttribute('data-modal-group-status', 'active');
}
});
});
// Close modal
document.querySelectorAll('[data-modal-close]').forEach((closeBtn) => {
closeBtn.addEventListener('click', closeAllModals);
});
// Close modal on `Escape` key
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape') {
closeAllModals();
}
});
// Function to close all modals
function closeAllModals() {
modalTargets.forEach((target) => target.setAttribute('data-modal-status', 'not-active'));
if (modalGroup) {
modalGroup.setAttribute('data-modal-group-status', 'not-active');
}
}
}
// Initialize Basic Modal
document.addEventListener('DOMContentLoaded', () => {
initModalBasic();
});Attributes
| Name | Type | Default | Description |
|---|---|---|---|
| data-modal-group-status | "not-active" | "active" | "not-active" | Applied to the outermost modal wrapper. Set to "active" when any modal in the group is open — controls the fixed overlay visibility via CSS. |
| data-modal-name | string | — | Identifies a modal card. Value must match the corresponding [data-modal-target] on the trigger element. |
| data-modal-status | "not-active" | "active" | "not-active" | Applied to each modal card and its trigger button. Set to "active" on the targeted card when it is opened; all others are reset to "not-active". |
| data-modal-target | string | — | Applied to trigger elements. Value must match the [data-modal-name] of the modal to open. |
| data-modal-close | "" | — | Applied to any element (backdrop, close button, etc.) inside the modal group that should close all modals when clicked. |
Notes
- •No external dependencies — pure vanilla JS.
- •Multiple named modals can coexist in a single group; only the targeted one is set to active at a time.
- •Closing resets all [data-modal-status] targets and the group wrapper simultaneously.
- •The [data-modal-close] attribute can be placed on the backdrop, a close button, or any element inside the group.
Guide
Modal Group
Use the attribute [data-modal-group-status="not-active"] on a parent container to indicate the modal group's overall status. This container will receive the attribute [data-modal-group-status="active"] when any modal in the group is active. This can be used to style the full modal group — for example, the dark background behind the modal elements.
Opening a modal
Add the attribute [data-modal-target="example"] to the element that triggers a modal. The value of this attribute should match the corresponding modal's [data-modal-name] attribute.
<button data-modal-target="example-modal" data-modal-status="not-active">Open Modal</button>Modals
Add [data-modal-name="example"] to each modal. The value of this attribute should match the [data-modal-target] value of the triggering element. The [data-modal-status="active"] attribute will be used to show and style the activated button and/or modal.
Modal Close Button
Add the attribute [data-modal-close] to any element inside the [data-modal-group-status] element that should close it. The Escape key is also supported to close the modal.