Modale Accessibile
by murgiaMarco
HTML
<button id="openModal">Apri Modale</button>
<div class="overlay" id="overlay">
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" aria-describedby="modalDesc">
<h2 id="modalTitle">Titolo Modale</h2>
<p id="modalDesc">Questa è una modale accessibile per screen reader e gesti mobile.</p>
<button id="closeModal">Chiudi</button>
</div>
</div>
CSS
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.overlay {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.5);
display: none;
align-items: center;
justify-content: center;
}
.modal {
background: #fff;
padding: 20px;
border-radius: 8px;
width: 90%;
max-width: 400px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
.modal h2 {
margin-top: 0;
}
.modal button {
margin-top: 20px;
padding: 10px;
background: #0078d7;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
.modal button:hover {
background: #005fa3;
}
JavaScript
const overlay = document.getElementById('overlay');
const openBtn = document.getElementById('openModal');
const closeBtn = document.getElementById('closeModal');
const modal = overlay.querySelector('.modal');
let focusableElements;
let firstFocusable;
let lastFocusable;
let startX = 0;
// Apri modale
openBtn.addEventListener('click', () => {
overlay.style.display = 'flex';
overlay.setAttribute('aria-hidden', 'false');
trapFocus();
firstFocusable.focus();
});
// Chiudi modale
function closeModal() {
overlay.style.display = 'none';
overlay.setAttribute('aria-hidden', 'true');
openBtn.focus();
}
closeBtn.addEventListener('click', closeModal);
// Chiudi con ESC
document.addEventListener('keydown', (e) => {
if (overlay.style.display === 'flex' && e.key === 'Escape') {
closeModal();
}
});
// Focus trap
function trapFocus() {
focusableElements = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
firstFocusable = focusableElements[0];
lastFocusable = focusableElements[focusableElements.length - 1];
document.addEventListener('keydown', (e) => {
if (overlay.style.display === 'flex' && e.key === 'Tab') {
if (e.shiftKey) {
if (document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable.focus();
}
} else {
if (document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable.focus();
}
}
}
});
}
// Swipe per chiudere (opzionale)
modal.addEventListener('touchstart', (e) => {
startX = e.touches[0].clientX;
});
modal.addEventListener('touchend', (e) => {
const endX = e.changedTouches[0].clientX;
if (startX - endX > 80) { // Swipe verso sinistra
closeModal();
}
});