Interactive Modal with CSS Transitions
Create a stylish modal dialog that smoothly appears and disappears using CSS transitions and JavaScript.
Code
<!--
Interactive Modal with CSS Transitions (Accessible + reusable)
Usage: call window.modal.open() / window.modal.close() or click the "Open modal" button.
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Interactive Modal with CSS Transitions</title>
<style>
:root { --dur: 240ms; --ease: cubic-bezier(.2,.8,.2,1); }
/* Overlay uses opacity/visibility for smooth transitions (avoid display:none flicker). */
.modal {
position: fixed; inset: 0; z-index: 1000;
display: grid; place-items: center;
background: rgba(0,0,0,.55);
opacity: 0; visibility: hidden; pointer-events: none;
transition: opacity var(--dur) var(--ease), visibility 0s linear var(--dur);
}
.modal.is-open {
opacity: 1; visibility: visible; pointer-events: auto;
transition: opacity var(--dur) var(--ease);
}
.modal-content {
width: min(560px, 92vw);
background: #fff; border: 1px solid rgba(0,0,0,.12);
border-radius: 14px; padding: 18px 18px 16px;
box-shadow: 0 18px 60px rgba(0,0,0,.25);
transform: translateY(10px) scale(.98);
transition: transform var(--dur) var(--ease);
}
.modal.is-open .modal-content { transform: translateY(0) scale(1); }
.close {
appearance: none; border: 0; background: transparent; cursor: pointer;
float: right; line-height: 1; padding: 6px;
font-size: 28px; font-weight: 700; color: #8a8a8a;
}
.close:hover { color: #333; }
/* Respect reduced motion preferences. */
@media (prefers-reduced-motion: reduce) {
.modal, .modal-content { transition: none !important; }
}
</style>
</head>
<body>
<button type="button" id="openModalBtn">Open modal</button>
<!-- Required structure (kept), with accessibility attributes added. -->
<div id="myModal" class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" aria-hidden="true">
<div class="modal-content" role="document" tabindex="-1">
<button class="close" type="button" aria-label="Close modal">×</button>
<h2 id="modalTitle">Modal Header</h2>
<p>This is a simple modal dialog!</p>
</div>
</div>
<script>
/* Lightweight modal controller: handles open/close, ESC, outside click, and focus management. */
(() => {
const modal = document.getElementById('myModal');
const content = modal.querySelector('.modal-content');
const closeBtn = modal.querySelector('.close');
const openBtn = document.getElementById('openModalBtn');
if (!modal || !content || !closeBtn) return;
let lastActiveEl = null;
const setOpen = (open) => {
modal.classList.toggle('is-open', open);
modal.setAttribute('aria-hidden', String(!open));
if (open) {
lastActiveEl = document.activeElement;
// Next frame ensures transitions apply consistently.
requestAnimationFrame(() => content.focus({ preventScroll: true }));
} else if (lastActiveEl && typeof lastActiveEl.focus === 'function') {
lastActiveEl.focus({ preventScroll: true });
lastActiveEl = null;
}
};
const open = () => setOpen(true);
const close = () => setOpen(false);
// Close on ESC (common expected behavior).
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.classList.contains('is-open')) close();
});
// Close when clicking the dimmed overlay (but not when clicking the dialog).
modal.addEventListener('click', (e) => { if (e.target === modal) close(); });
openBtn?.addEventListener('click', open);
closeBtn.addEventListener('click', close);
// Example usage:
// open(); // show on page load
window.modal = { open, close };
})();
</script>
</body>
</html>