Skip to content

Instantly share code, notes, and snippets.

@brandonhimpfen
Created February 8, 2026 22:59
Show Gist options
  • Select an option

  • Save brandonhimpfen/5a78c5c07f41d3a368d794448c5861b5 to your computer and use it in GitHub Desktop.

Select an option

Save brandonhimpfen/5a78c5c07f41d3a368d794448c5861b5 to your computer and use it in GitHub Desktop.
Minimal accessible HTML modal markup (ARIA + focus target) with a tiny JS controller (open/close + Escape + backdrop click).
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Minimal Accessible Modal</title>
<style>
/* Minimal styling just to make the demo usable */
.modal-backdrop[hidden] { display: none; }
.modal-backdrop {
position: fixed; inset: 0;
background: rgba(0,0,0,0.5);
display: grid; place-items: center;
padding: 1rem;
}
.modal {
background: #fff;
width: min(520px, 100%);
border-radius: 12px;
padding: 1rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.25);
}
.modal-header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.modal-title { margin: 0; font-size: 1.1rem; }
.modal-close { font: inherit; }
</style>
</head>
<body>
<!-- Trigger -->
<button type="button" data-modal-open="demo-modal">
Open modal
</button>
<!-- Backdrop -->
<div
class="modal-backdrop"
id="demo-modal-backdrop"
hidden
>
<!--
role="dialog" + aria-modal="true" indicates a modal dialog.
aria-labelledby points to the title element inside.
aria-describedby points to the main description text.
-->
<div
class="modal"
role="dialog"
aria-modal="true"
aria-labelledby="demo-modal-title"
aria-describedby="demo-modal-desc"
data-modal="demo-modal"
tabindex="-1"
>
<div class="modal-header">
<h2 class="modal-title" id="demo-modal-title">Modal title</h2>
<button type="button" class="modal-close" data-modal-close aria-label="Close dialog">
</button>
</div>
<div id="demo-modal-desc">
<p>This is minimal accessible modal markup with basic open/close logic.</p>
<p>Press Escape to close, or click the backdrop.</p>
</div>
<div style="margin-top: 1rem; display: flex; gap: .5rem; justify-content: flex-end;">
<button type="button" data-modal-close>Cancel</button>
<button type="button">Confirm</button>
</div>
</div>
</div>
<script>
// Minimal accessible modal controller:
// - Opens/closes modal
// - Restores focus to the trigger
// - Closes on Escape
// - Closes on backdrop click
// Note: For a fully robust solution, add focus trapping. This is kept minimal on purpose.
const openButtons = document.querySelectorAll("[data-modal-open]");
const closeSelectors = "[data-modal-close]";
let lastActiveElement = null;
function openModal(modalId) {
const modal = document.querySelector(`[data-modal="${modalId}"]`);
const backdrop = document.getElementById(`${modalId}-backdrop`);
if (!modal || !backdrop) return;
lastActiveElement = document.activeElement;
backdrop.hidden = false;
// Prevent background scroll (optional but common)
document.body.style.overflow = "hidden";
// Focus the dialog container (or choose the first focusable control)
modal.focus();
}
function closeModal(modalId) {
const backdrop = document.getElementById(`${modalId}-backdrop`);
if (!backdrop) return;
backdrop.hidden = true;
document.body.style.overflow = "";
// Restore focus to the element that opened the modal
if (lastActiveElement && typeof lastActiveElement.focus === "function") {
lastActiveElement.focus();
}
lastActiveElement = null;
}
// Open handlers
openButtons.forEach(btn => {
btn.addEventListener("click", () => openModal(btn.dataset.modalOpen));
});
// Close handlers (buttons inside)
document.addEventListener("click", (e) => {
const closeBtn = e.target.closest(closeSelectors);
if (!closeBtn) return;
const modal = closeBtn.closest("[data-modal]");
if (!modal) return;
closeModal(modal.dataset.modal);
});
// Close on Escape
document.addEventListener("keydown", (e) => {
if (e.key !== "Escape") return;
const openBackdrop = document.querySelector(".modal-backdrop:not([hidden])");
if (!openBackdrop) return;
const openModalEl = openBackdrop.querySelector("[data-modal]");
if (!openModalEl) return;
closeModal(openModalEl.dataset.modal);
});
// Close on backdrop click (but not when clicking inside the dialog)
document.addEventListener("click", (e) => {
const backdrop = e.target.classList && e.target.classList.contains("modal-backdrop") ? e.target : null;
if (!backdrop || backdrop.hidden) return;
const modal = backdrop.querySelector("[data-modal]");
if (!modal) return;
closeModal(modal.dataset.modal);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment