Skip to content

Instantly share code, notes, and snippets.

@yaodong
Created March 11, 2025 16:56
Show Gist options
  • Save yaodong/8fb183d2a728d8bfba5c2a92477c2c80 to your computer and use it in GitHub Desktop.
Save yaodong/8fb183d2a728d8bfba5c2a92477c2c80 to your computer and use it in GitHub Desktop.
example - modal html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Animated Modal</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Modal visibility control */
[data-modal][data-state="closed"] {
visibility: hidden;
}
[data-modal][data-state="open"] {
visibility: visible;
}
[data-modal="backdrop"] {
transition-property: opacity;
transition-duration: 300ms;
}
[data-modal][data-state="open"] [data-modal="backdrop"] {
opacity: 0.5;
}
[data-modal][data-state="closed"] [data-modal="backdrop"] {
opacity: 0;
}
/* Container animations */
[data-modal="container"] {
transition-property: opacity;
transition-duration: 300ms;
}
[data-modal][data-state="open"] [data-modal="container"] {
opacity: 1;
pointer-events: auto;
}
[data-modal][data-state="closed"] [data-modal="container"] {
opacity: 0;
pointer-events: none;
}
/* Content animations - combined zoom and fade effects */
[data-modal="content"] {
transition-property: all;
transition-duration: 300ms;
transform: scale(0.95) translateY(-1rem);
}
[data-modal][data-state="open"] [data-modal="content"] {
transform: scale(1) translateY(0);
}
[data-modal][data-state="closed"] [data-modal="content"] {
transform: scale(0.95) translateY(-1rem);
}
</style>
</head>
<body>
<div class="flex items-center justify-center min-h-screen bg-gray-100 p-4">
<!-- Trigger Button -->
<button
id="openModalBtn"
class="px-4 py-2 bg-purple-600 text-white font-medium rounded-lg shadow-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-opacity-50 transition-all duration-300"
aria-haspopup="dialog"
>
Open Beautiful Modal
</button>
<!-- Modal with improved accessibility and animation -->
<div
id="modal"
data-modal="root"
data-state="closed"
aria-hidden="true"
class="hidden"
>
<!-- Modal Backdrop -->
<div
class="fixed inset-0 bg-black z-40"
data-modal="backdrop"
aria-hidden="true"
></div>
<!-- Modal Container -->
<div
class="fixed inset-0 flex items-center justify-center p-4 z-50"
data-modal="container"
>
<div
class="bg-white rounded-xl shadow-xl p-6 max-w-md w-full mx-auto"
data-modal="content"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-description"
tabindex="-1"
>
<!-- Modal Header -->
<div class="flex justify-between items-center border-b border-gray-200 pb-3 mb-4">
<h3 id="modal-title" class="text-xl font-semibold text-gray-800">
Beautiful Modal
</h3>
<button
id="closeModalBtn"
class="text-gray-400 hover:text-gray-600 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-gray-400 rounded-sm"
aria-label="Close modal"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<!-- Modal Body -->
<div id="modal-description" class="mb-6">
<p class="text-gray-600 mb-4">
This is a beautiful modal created with Tailwind CSS and vanilla JavaScript. It has smooth fade-in and fade-out animations,
along with scaling and slide effects for a polished user experience.
</p>
<p class="text-gray-600">
The modal now uses a single <code>data-modal</code> attribute with different values, making the code even more
semantic and maintainable.
</p>
</div>
<!-- Modal Footer -->
<div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-3">
<button
id="cancelBtn"
class="px-4 py-2 mt-3 sm:mt-0 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-opacity-50"
>
Cancel
</button>
<button
id="confirmBtn"
class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-opacity-50"
>
Confirm
</button>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get DOM elements
const openModalBtn = document.getElementById('openModalBtn');
const closeModalBtn = document.getElementById('closeModalBtn');
const cancelBtn = document.getElementById('cancelBtn');
const confirmBtn = document.getElementById('confirmBtn');
const modal = document.getElementById('modal');
const modalContent = modal.querySelector('[data-modal="content"]');
// Trap focus within modal when open
function trapFocus(element) {
const focusableElements = element.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
const firstFocusableElement = focusableElements[0];
const lastFocusableElement = focusableElements[focusableElements.length - 1];
element.addEventListener('keydown', function(e) {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstFocusableElement) {
e.preventDefault();
lastFocusableElement.focus();
} else if (!e.shiftKey && document.activeElement === lastFocusableElement) {
e.preventDefault();
firstFocusableElement.focus();
}
}
if (e.key === 'Escape') {
closeModal();
}
});
}
// Open modal function
function openModal() {
// First remove hidden class so the modal is in the DOM
modal.classList.remove('hidden');
// Small delay to ensure DOM is updated before animations
requestAnimationFrame(() => {
// Then update state to trigger animations
modal.setAttribute('data-state', 'open');
modal.setAttribute('aria-hidden', 'false');
// Focus the first element and trap focus
setTimeout(() => {
modalContent.focus();
trapFocus(modalContent);
}, 50);
});
}
// Close modal function
function closeModal() {
// Update state to trigger animations
modal.setAttribute('data-state', 'closed');
modal.setAttribute('aria-hidden', 'true');
// Add a delay before hiding completely to allow for animations
setTimeout(() => {
modal.classList.add('hidden');
}, 300);
// Return focus to trigger button
openModalBtn.focus();
}
// Event listeners
openModalBtn.addEventListener('click', openModal);
closeModalBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', closeModal);
// Event delegation for backdrop click
modal.addEventListener('click', function(event) {
if (event.target.hasAttribute('data-modal') && event.target.getAttribute('data-modal') === 'backdrop') {
closeModal();
}
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment