Chapter 78: Event Handling in HTML & JavaScript

Accessible Keyboard Events — Focus Traps, Shortcuts & WCAG Standards

Engineering WCAG 2.2 compliant keyboard interactions using `keydown`, `keyup`, `e.key` vs `e.code`, and robust modal focus trap loops.

LEARNING OBJECTIVES
  • Differentiate between semantic key values (event.key), physical key positions (event.code), and legacy key codes (event.keyCode).
  • Handle internationalized text input and IME (Input Method Editor) character composition using event.isComposing.
  • Implement robust, accessible modal focus traps compliant with WCAG 2.1.1 (Keyboard) and 2.1.2 (No Keyboard Trap).
  • Build high-performance keyboard shortcut dispatchers using modifier flags (shiftKey, ctrlKey, altKey, metaKey).
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

Imagine navigating a skyscraper where all elevators and stairs have been sealed, and your only method of movement is a sequential optical scanner stepping through one doorway at a time:

[ ENTRY DOOR (Trigger Button) ] ──> Clicks Open
        |
        v  (Focus moves inside room)
+-------------------------------------------------------------------------------+
|                           ISOLATED MODAL ROOM                                 |
|                                                                               |
|  [First Input] <── (Tab) ──> [Checkbox] <── (Tab) ──> [Close Button (Last)]  |
|       ^                                                      |                |
|       +──────────────── (Tab loops forward) ─────────────────+                |
|       +────────────── (Shift+Tab loops backward) ────────────+                |
+-------------------------------------------------------------------------------+
        |
        v  (Presses ESC or Close Button)
[ ENTRY DOOR (Trigger Button) ] <── Focus Restored!

Millions of users—including screen reader users, motor-impaired individuals, power users, and developers—navigate web applications exclusively using keyboards. If a modal dialog opens and tabbing moves focus behind the modal into the obscured page, the application fails fundamental accessibility standards.


Technical Deep Dive & Specifications

1. event.key vs event.code vs event.keyCode

Property Description Example (US Layout) Example (French AZERTY) Usage Recommendation
event.key The printable character or semantic function produced by the keypress. "a", "A", "Enter", "Escape" "a", "A", "Enter", "Escape" Standard for UI Actions & Shortcuts
event.code The physical hardware key location on the physical keyboard layout. "KeyQ" "KeyA" (Same physical key slot) Standard for Games (WASD Movement)
event.keyCode Deprecated numerical ASCII code. 65 65 Do NOT Use (Deprecated)
window.addEventListener('keydown', (e) => {
  console.log({
    key: e.key,        // "Escape", "Enter", "ArrowDown", "a", "A"
    code: e.code,      // "Escape", "Enter", "ArrowDown", "KeyA"
    shiftKey: e.shiftKey,
    ctrlKey: e.ctrlKey,
    altKey: e.altKey,
    metaKey: e.metaKey // Command on Mac, Windows key on PC
  });
});

2. IME Composition (event.isComposing)

When users type in languages requiring character composition (Japanese Kanji, Chinese Pinyin, Korean Hangul), the operating system displays an intermediate composition window.

  • During composition, the user presses Enter to confirm phonetic character choices, not to submit the form!
  • Rule: Always check if (event.isComposing || event.keyCode === 229) return; before processing Enter or Escape keys.

3. The Accessible Modal Focus Trap Algorithm

To conform to WCAG 2.2 Success Criterion 2.1.2 (No Keyboard Trap):

  1. Save Previous Active Element: Before opening the dialog, save const previousActiveElement = document.activeElement;.
  2. Find Focusable Descendants: Query all focusable nodes inside the modal:
    const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
    const focusableElements = modal.querySelectorAll(FOCUSABLE_SELECTOR);
    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];
    
  3. Trap Focus on Tab / Shift+Tab:
    • If e.key === 'Tab' and e.shiftKey (backward tab) on firstElement, prevent default and focus lastElement.
    • If e.key === 'Tab' and !e.shiftKey (forward tab) on lastElement, prevent default and focus firstElement.
  4. Listen for Escape: Close modal and restore focus to previousActiveElement.focus().
  5. Background Inertness: Add the inert attribute to background siblings (<main inert>, <header inert>) so assistive tech cannot interact with background elements while the modal is open.

💻 Interactive Code Playground

Starter Code

Line-by-Line Code Breakdown

  • Line 57 (previousActiveElement = document.activeElement;): Saves the button that launched the modal so focus can be returned when the dialog closes.
  • Line 59 (mainContent.setAttribute('inert', '')): Standard HTML inert attribute disables pointer events, tab focusing, and screen reader access for background elements.
  • Lines 82–98 (handleKeyDown): Intercepts Tab navigation. When a user presses Tab on the "Save Changes" button, focus wraps seamlessly to the first <input>.
  • Line 72 (previousActiveElement.focus()): WCAG requirement: restoring focus back to the opener button ensures users do not lose their place in the document.

Expected Browser Render Output

  • Pressing Tab inside the modal endlessly cycles between the two input fields and two buttons.
  • Pressing Escape closes the modal immediately and places the focus outline back on "Open Account Settings".

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

🏋️ Hands-On Exercise

🎯 The Challenge: Build a Roving Tabindex Toolbar

Instructions:

  1. Create a rich text editor toolbar (<div role="toolbar" id="toolbar">) containing 4 buttons: Bold, Italic, Underline, and Code.
  2. Implement the Roving Tabindex Pattern:
    • Only the currently active toolbar button has tabindex="0"; all other buttons have tabindex="-1".
    • Pressing ArrowRight moves focus to the next button (wrapping from last to first).
    • Pressing ArrowLeft moves focus to the previous button (wrapping from first to last).
    • Pressing Home focuses the first button, and End focuses the last button.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. Using event.keyCode: keyCode is deprecated. Writing if (e.keyCode === 27) fails in modern TypeScript strict mode. Always use if (e.key === 'Escape').
  2. Ignoring IME Input: Submitting a form on Enter without checking e.isComposing disrupts Asian language users composing characters.
  3. Creating Accidental Infinite Focus Loops: Tabbing into non-focusable elements will break focus cycling. Always filter focusable queries with :not([disabled]):not([tabindex="-1"]).

💡 Pro Tips

  1. The Native <dialog> Element: In modern HTML, <dialog>.showModal() automatically implements backdrop isolation, Escape key closing, and focus restoration out of the box!
  2. Mac vs Windows Shortcut Normalization: Support both platforms by checking const isCmdOrCtrl = e.metaKey || e.ctrlKey; for shortcuts like Cmd+S / Ctrl+S.

📌 Key Takeaways

  • Use event.key for semantic character checks ("Enter", "Escape") and event.code for physical key slots ("KeyW").
  • Always guard with if (event.isComposing) return; to support international IME composition.
  • Modal dialogs must trap focus with Tab / Shift+Tab, close on Escape, and restore focus to the opening element.
  • Roving tabindex (0 on active, -1 on siblings) provides accessible arrow key navigation inside toolbars, tabs, and menus.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

Why should you use event.key === 'Escape' instead of event.code === 'Escape' for standard UI keyboard dismissal?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

What is the purpose of checking event.isComposing during keyboard event handling?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

According to WCAG accessibility guidelines, what critical step must occur when a modal dialog is closed?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP