Code

js/components/modal.js — an accessible modal

In the code column — js/components/modal.js. All the accessibility is in two functions:

export function openModal(date) {
  overlay.classList.remove('hidden');
  restoreBackground = hideBackground(overlay);              // hide the background
  releaseFocus = trapFocus(overlay, { initialFocus: notesInput });   // move in + trap
}

export function closeModal() {
  overlay.classList.add('hidden');
  restoreBackground?.();
  releaseFocus?.();          // restores focus to the button that opened us
}

Plus Esc closes and a backdrop click closes:

overlay.addEventListener('click', (e) => { if (e.target === overlay) closeModal(); });
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && !overlay.classList.contains('hidden')) closeModal();
});

(Backdrop click: e.target === overlay — only if the overlay itself was clicked, not the modal inside it.)

The confirm dialog (confirm.js) and toasts (toast.js) are the same pattern: confirm.js uses the same trapFocus; toast.js just adds a transient <div> that removes itself after ~2.6 s.

The star keyboard (makeRadioGroup from a11y.js) — arrow keys move, Space/Enter selects:

makeRadioGroup(starsEl, { onSelect: (value) => { draftRating = value; renderStars(); } });
// role="radio", tabIndex management, ArrowLeft/Right → next star

Verify (keyboard, not mouse!).

1. Open the modal → focus is already inside (the notes field).
2. Press Tab several times → focus CYCLES within the modal, never leaves.
3. Close (Esc) → focus returns to the "Edit" button you came from.
4. Stars: move with arrows, pick with Space — no mouse.

Gotcha (focus escapes). In openModal, comment out the trapFocus line. Open the modal and press Tab — focus travels off to the nav links behind the modal (you'll see the focus ring in the wrong place). A keyboard user has now "vanished" into the background. Restore trapFocus — focus cycles inside again. Every modal must trap focus; without it, it's broken for accessibility even if it looks fine with a mouse.