Tailwind dynamic modal with javascript

Check the code

<!-- Button to Open Modal -->
<button class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors duration-200" data-modal="exampleModal">
    Open Modal
</button>

<!-- Modal -->
<div id="exampleModal" class="fixed inset-0 hidden opacity-0 bg-black bg-opacity-50 flex items-center justify-center p-4 transition-opacity duration-200" aria-hidden="true" aria-labelledby="modalTitle" aria-describedby="modalDescription">
    <div class="bg-white p-6 rounded-lg shadow-lg w-full max-w-md">
        <!-- Modal Header -->
        <div class="flex justify-between items-center border-b pb-2">
            <h2 id="modalTitle" class="text-lg font-semibold">Modal Title</h2>
            <button class="text-gray-500 hover:text-gray-700 close-modal" aria-label="Close modal">&times;</button>
        </div>

        <!-- Modal Body -->
        <div class="py-4">
            <p id="modalDescription" class="text-gray-700">This is a simple modal using Tailwind CSS.</p>
        </div>

        <!-- Modal Footer -->
        <div class="flex justify-end gap-2 mt-4 border-t pt-2">
            <button class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400 transition-colors duration-200 close-modal">Close</button>
            <button class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors duration-200">Save Changes</button>
        </div>
    </div>
</div>

<script>
class CustomModal {
    constructor(modalId) {
        this.modal = document.getElementById(modalId);
        if (!this.modal) {
            throw new Error(`Modal with ID "${modalId}" not found.`);
        }
        this.closeButtons = this.modal.querySelectorAll(".close-modal");
        this.initEvents();
    }

    open() {
        this.modal.classList.remove("hidden"); // Remove hidden class
        setTimeout(() => {
            this.modal.classList.remove("opacity-0"); // Fade in
        }, 10); // Small delay to allow display change
        document.body.style.overflow = "hidden"; // Prevent scrolling
        this.modal.setAttribute("aria-hidden", "false"); // Update ARIA attribute
    }

    close() {
        this.modal.classList.add("opacity-0"); // Fade out
        setTimeout(() => {
            this.modal.classList.add("hidden"); // Hide after transition
        }, 200); // Match transition duration
        document.body.style.overflow = "auto"; // Restore scrolling
        this.modal.setAttribute("aria-hidden", "true"); // Update ARIA attribute
    }

    initEvents() {
        // Close modal when clicking on close buttons
        this.closeButtons.forEach(button => {
            button.addEventListener("click", () => this.close());
        });

        // Close modal when clicking outside the modal
        window.addEventListener("click", (e) => {
            if (e.target === this.modal) {
                this.close();
            }
        });

        // Close modal when pressing the Escape key
        window.addEventListener("keydown", (e) => {
            if (e.key === "Escape") {
                this.close();
            }
        });
    }
}

// Global modal handler
const modals = {};

// Initialize modals after the DOM is fully loaded
document.addEventListener("DOMContentLoaded", () => {
    document.querySelectorAll("[data-modal]").forEach(button => {
        const modalId = button.getAttribute("data-modal");
        if (!modals[modalId]) {
            modals[modalId] = new CustomModal(modalId);
        }
        button.addEventListener("click", () => modals[modalId].open());
    });
});
</script>