Modals
Glass Modal
A frosted-glass confirmation dialog with overlay dismiss and Escape-to-close.
HTML/CSS/JSavailableIntermediate
modaldialogglassoverlay
Author: Prism Bits @saiusesgithub
Live iframe preview
<button class="glass-modal-trigger" type="button" data-modal-open>Open modal</button>
<div class="glass-modal-overlay" data-modal hidden>
<div class="glass-modal" role="dialog" aria-modal="true" aria-labelledby="glass-modal-title">
<h2 class="glass-modal__title" id="glass-modal-title">Enable notifications?</h2>
<p class="glass-modal__body">
We'll ping you when a new component lands. You can turn this off any time.
</p>
<div class="glass-modal__actions">
<button class="glass-modal__button glass-modal__button--primary" type="button" data-modal-close>
Enable
</button>
<button class="glass-modal__button" type="button" data-modal-close>Not now</button>
</div>
</div>
</div>
<style>
.glass-modal-trigger {
padding: 12px 22px;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 14px;
background: rgba(255, 255, 255, 0.08);
color: #f4f5fb;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s ease, transform 0.2s ease;
}
.glass-modal-trigger:hover {
background: rgba(255, 255, 255, 0.14);
transform: translateY(-1px);
}
.glass-modal-trigger:focus-visible,
.glass-modal__button:focus-visible {
outline: 2px solid rgba(167, 139, 250, 0.9);
outline-offset: 2px;
}
.glass-modal-overlay[hidden] {
display: none;
}
.glass-modal-overlay {
position: fixed;
inset: 0;
display: grid;
place-items: center;
padding: 24px;
background: rgba(5, 6, 12, 0.6);
backdrop-filter: blur(6px);
animation: glass-modal-fade 0.2s ease;
}
.glass-modal {
width: min(100%, 380px);
padding: 26px;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 20px;
background: linear-gradient(160deg, rgba(30, 32, 48, 0.92), rgba(14, 15, 26, 0.92));
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5);
animation: glass-modal-rise 0.25s ease;
}
.glass-modal__title {
margin: 0;
font-size: 1.25rem;
color: #f4f5fb;
}
.glass-modal__body {
margin: 10px 0 0;
font-size: 0.9rem;
line-height: 1.55;
color: rgba(244, 245, 251, 0.6);
}
.glass-modal__actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 22px;
}
.glass-modal__button {
padding: 10px 18px;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 12px;
background: rgba(255, 255, 255, 0.06);
color: #f4f5fb;
font-size: 0.88rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s ease;
}
.glass-modal__button:hover {
background: rgba(255, 255, 255, 0.12);
}
.glass-modal__button--primary {
border: 0;
background: #f4f5fb;
color: #101322;
}
.glass-modal__button--primary:hover {
background: #ffffff;
}
@keyframes glass-modal-fade {
from {
opacity: 0;
}
}
@keyframes glass-modal-rise {
from {
opacity: 0;
transform: translateY(14px) scale(0.97);
}
}
@media (prefers-reduced-motion: reduce) {
.glass-modal-overlay,
.glass-modal {
animation: none;
}
}
</style>
<script>
const overlay = document.querySelector("[data-modal]");
const openButton = document.querySelector("[data-modal-open]");
const dialog = overlay.querySelector(".glass-modal");
const FOCUSABLE =
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
function getFocusable() {
return Array.from(dialog.querySelectorAll(FOCUSABLE)).filter(
(el) => el.offsetParent !== null,
);
}
function openModal() {
overlay.hidden = false;
// aria-modal="true" promises the rest of the page is inert — make it so.
openButton.setAttribute("inert", "");
const focusable = getFocusable();
(focusable[0] || dialog).focus();
}
function closeModal() {
overlay.hidden = true;
openButton.removeAttribute("inert");
openButton.focus();
}
// Keep Tab / Shift+Tab cycling within the dialog instead of escaping to the
// (now inert) background.
function trapTab(event) {
const focusable = getFocusable();
if (focusable.length === 0) {
event.preventDefault();
return;
}
const first = focusable[0];
const last = focusable[focusable.length - 1];
const active = document.activeElement;
if (!dialog.contains(active)) {
event.preventDefault();
first.focus();
return;
}
if (event.shiftKey && active === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && active === last) {
event.preventDefault();
first.focus();
}
}
openButton.addEventListener("click", openModal);
overlay.addEventListener("click", (event) => {
if (event.target === overlay || event.target.closest("[data-modal-close]")) {
closeModal();
}
});
document.addEventListener("keydown", (event) => {
if (overlay.hidden) return;
if (event.key === "Escape") {
closeModal();
} else if (event.key === "Tab") {
trapTab(event);
}
});
</script>