🔲 Modal Dialogs Design Guide
Modal dialogs (або "modal windows", "overlays") interrupting user flow для presenting critical information, collecting input, або confirming actions. When properly designed, modals focus attention на single task while dimming background content. For mushroom education site, modals handle: dosage calculator results, safety warnings before downloading guides, image lightboxes для species photos, trip report submissions. Effective modal design balances interruption justification (only для important actions), clarity (obvious close mechanism), accessibility (keyboard navigation, focus trap, screen reader support), та animations (smooth entrance/exit). Цей guide охоплює complete modal anatomy, CSS/JavaScript implementations, ARIA patterns, focus management, та mobile considerations.
🎨 Modal Demo
Click button to see working modal:
💻 HTML Structure
Complete Modal Markup:
<!-- Modal Overlay -->
<div class="modal-overlay" id="myModal" role="dialog" aria-labelledby="modalTitle" aria-modal="true">
<!-- Modal Dialog -->
<div class="modal-dialog">
<!-- Header -->
<div class="modal-header">
<h2 class="modal-title" id="modalTitle">Modal Title</h2>
<button class="modal-close" aria-label="Close modal">×</button>
</div>
<!-- Content -->
<div class="modal-content">
<p>Modal content goes here...</p>
</div>
<!-- Footer (optional) -->
<div class="modal-footer">
<button class="btn-secondary">Cancel</button>
<button class="btn-primary">Confirm</button>
</div>
</div>
</div>
🎨 CSS Styling
Modal Overlay та Backdrop:
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6); /* Dark backdrop */
backdrop-filter: blur(4px); /* Blur background (modern browsers) */
display: flex;
align-items: center;
justify-content: center;
z-index: 10000; /* Above everything */
/* Hidden by default */
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
/* Active state */
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
Modal Dialog:
.modal-dialog {
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 500px; /* Adjustable */
width: 90%;
max-height: 90vh; /* Fits viewport */
overflow: hidden;
/* Animation: scale + slide up */
transform: scale(0.9) translateY(-20px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.modal-overlay.active .modal-dialog {
transform: scale(1) translateY(0);
}
Modal Header, Content, Footer:
/* Header */
.modal-header {
padding: 25px 30px;
border-bottom: 1px solid #e9ecef;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
font-size: 1.5em;
font-weight: 700;
color: #2c3e50;
margin: 0;
}
/* Close button */
.modal-close {
width: 36px;
height: 36px;
border: none;
background: transparent;
color: #6c757d;
font-size: 1.5em;
cursor: pointer;
border-radius: 50%;
transition: all 0.2s;
}
.modal-close:hover {
background: #f8f9fa;
color: #2c3e50;
}
/* Content */
.modal-content {
padding: 30px;
overflow-y: auto; /* Scrollable if content tall */
max-height: calc(90vh - 140px); /* Account для header/footer */
}
/* Footer */
.modal-footer {
padding: 20px 30px;
border-top: 1px solid #e9ecef;
display: flex;
justify-content: flex-end;
gap: 12px;
}
⚙️ JavaScript Implementation
Open/Close Functions:
// Open modal
function openModal(modalId) {
const modal = document.getElementById(modalId);
modal.classList.add('active');
document.body.style.overflow = 'hidden'; // Prevent background scroll
// Focus trap
trapFocus(modal);
// Store last focused element (для restoration)
modal.dataset.lastFocus = document.activeElement;
}
// Close modal
function closeModal(modalId) {
const modal = document.getElementById(modalId);
modal.classList.remove('active');
document.body.style.overflow = ''; // Restore scroll
// Restore focus
const lastFocus = document.querySelector(modal.dataset.lastFocus);
if (lastFocus) lastFocus.focus();
}
// Close on backdrop click
function closeOnBackdrop(event, modalId) {
if (event.target.classList.contains('modal-overlay')) {
closeModal(modalId);
}
}
// Close on ESC key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const activeModal = document.querySelector('.modal-overlay.active');
if (activeModal) {
closeModal(activeModal.id);
}
}
});
Focus Trap (Accessibility):
// Trap focus inside modal
function trapFocus(modal) {
const focusableElements = modal.querySelectorAll(
'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'
);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
// Focus first element
firstFocusable.focus();
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey) {
// Shift+Tab
if (document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable.focus();
}
} else {
// Tab
if (document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable.focus();
}
}
}
});
}
♿ Accessibility
ARIA Attributes:
<!-- role="dialog" indicates modal purpose -->
<div class="modal-overlay"
role="dialog"
aria-labelledby="modalTitle"
aria-describedby="modalDescription"
aria-modal="true">
<div class="modal-dialog">
<!-- Title для screen readers -->
<h2 id="modalTitle">Safety Warning</h2>
<!-- Description (if needed) -->
<p id="modalDescription">Important information about psilocybin legality</p>
</div>
</div>
Keyboard Navigation:
- ✅ TAB cycles through focusable elements inside modal
- ✅ ESC closes modal
- ✅ Focus trap prevents tabbing outside modal
- ✅ First element focused when modal opens
- ✅ Focus restored до last element after close
✅ Modal Design Checklist
Implementation Checklist:
- ☐ Backdrop overlay dark enough (60-80% opacity)
- ☐ Modal centered horizontally та vertically
- ☐ Close button clearly visible (top-right)
- ☐ ESC key closes modal
- ☐ Clicking backdrop closes modal
- ☐ Focus trapped inside modal
- ☐ First element receives focus on open
- ☐ Focus restored on close
- ☐ ARIA attributes: role, aria-labelledby, aria-modal
- ☐ Background scroll prevented (body overflow: hidden)
- ☐ Smooth animations (0.3s)
- ☐ Mobile: Full eller almost-full width та height